##// END OF EJS Templates
add missing help strings to HistoryManager configurables...
MinRK -
Show More
@@ -1,808 +1,811 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 264 Session number to retrieve. The current session is 0, and negative
265 265 numbers count back from current session, so -1 is previous session.
266 266
267 267 Returns
268 268 -------
269 269
270 270 (session_id [int], start [datetime], end [datetime], num_cmds [int],
271 271 remark [unicode])
272 272
273 273 Sessions that are running or did not exit cleanly will have `end=None`
274 274 and `num_cmds=None`.
275 275
276 276 """
277 277
278 278 if session <= 0:
279 279 session += self.session_number
280 280
281 281 query = "SELECT * from sessions where session == ?"
282 282 return self.db.execute(query, (session,)).fetchone()
283 283
284 284 @catch_corrupt_db
285 285 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
286 286 """Get the last n lines from the history database.
287 287
288 288 Parameters
289 289 ----------
290 290 n : int
291 291 The number of lines to get
292 292 raw, output : bool
293 293 See :meth:`get_range`
294 294 include_latest : bool
295 295 If False (default), n+1 lines are fetched, and the latest one
296 296 is discarded. This is intended to be used where the function
297 297 is called by a user command, which it should not return.
298 298
299 299 Returns
300 300 -------
301 301 Tuples as :meth:`get_range`
302 302 """
303 303 self.writeout_cache()
304 304 if not include_latest:
305 305 n += 1
306 306 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
307 307 (n,), raw=raw, output=output)
308 308 if not include_latest:
309 309 return reversed(list(cur)[1:])
310 310 return reversed(list(cur))
311 311
312 312 @catch_corrupt_db
313 313 def search(self, pattern="*", raw=True, search_raw=True,
314 314 output=False, n=None, unique=False):
315 315 """Search the database using unix glob-style matching (wildcards
316 316 * and ?).
317 317
318 318 Parameters
319 319 ----------
320 320 pattern : str
321 321 The wildcarded pattern to match when searching
322 322 search_raw : bool
323 323 If True, search the raw input, otherwise, the parsed input
324 324 raw, output : bool
325 325 See :meth:`get_range`
326 326 n : None or int
327 327 If an integer is given, it defines the limit of
328 328 returned entries.
329 329 unique : bool
330 330 When it is true, return only unique entries.
331 331
332 332 Returns
333 333 -------
334 334 Tuples as :meth:`get_range`
335 335 """
336 336 tosearch = "source_raw" if search_raw else "source"
337 337 if output:
338 338 tosearch = "history." + tosearch
339 339 self.writeout_cache()
340 340 sqlform = "WHERE %s GLOB ?" % tosearch
341 341 params = (pattern,)
342 342 if unique:
343 343 sqlform += ' GROUP BY {0}'.format(tosearch)
344 344 if n is not None:
345 345 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
346 346 params += (n,)
347 347 elif unique:
348 348 sqlform += " ORDER BY session, line"
349 349 cur = self._run_sql(sqlform, params, raw=raw, output=output)
350 350 if n is not None:
351 351 return reversed(list(cur))
352 352 return cur
353 353
354 354 @catch_corrupt_db
355 355 def get_range(self, session, start=1, stop=None, raw=True,output=False):
356 356 """Retrieve input by session.
357 357
358 358 Parameters
359 359 ----------
360 360 session : int
361 361 Session number to retrieve.
362 362 start : int
363 363 First line to retrieve.
364 364 stop : int
365 365 End of line range (excluded from output itself). If None, retrieve
366 366 to the end of the session.
367 367 raw : bool
368 368 If True, return untranslated input
369 369 output : bool
370 370 If True, attempt to include output. This will be 'real' Python
371 371 objects for the current session, or text reprs from previous
372 372 sessions if db_log_output was enabled at the time. Where no output
373 373 is found, None is used.
374 374
375 375 Returns
376 376 -------
377 377 An iterator over the desired lines. Each line is a 3-tuple, either
378 378 (session, line, input) if output is False, or
379 379 (session, line, (input, output)) if output is True.
380 380 """
381 381 if stop:
382 382 lineclause = "line >= ? AND line < ?"
383 383 params = (session, start, stop)
384 384 else:
385 385 lineclause = "line>=?"
386 386 params = (session, start)
387 387
388 388 return self._run_sql("WHERE session==? AND %s" % lineclause,
389 389 params, raw=raw, output=output)
390 390
391 391 def get_range_by_str(self, rangestr, raw=True, output=False):
392 392 """Get lines of history from a string of ranges, as used by magic
393 393 commands %hist, %save, %macro, etc.
394 394
395 395 Parameters
396 396 ----------
397 397 rangestr : str
398 398 A string specifying ranges, e.g. "5 ~2/1-4". See
399 399 :func:`magic_history` for full details.
400 400 raw, output : bool
401 401 As :meth:`get_range`
402 402
403 403 Returns
404 404 -------
405 405 Tuples as :meth:`get_range`
406 406 """
407 407 for sess, s, e in extract_hist_ranges(rangestr):
408 408 for line in self.get_range(sess, s, e, raw=raw, output=output):
409 409 yield line
410 410
411 411
412 412 class HistoryManager(HistoryAccessor):
413 413 """A class to organize all history-related functionality in one place.
414 414 """
415 415 # Public interface
416 416
417 417 # An instance of the IPython shell we are attached to
418 418 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
419 419 # Lists to hold processed and raw history. These start with a blank entry
420 420 # so that we can index them starting from 1
421 421 input_hist_parsed = List([""])
422 422 input_hist_raw = List([""])
423 423 # A list of directories visited during session
424 424 dir_hist = List()
425 425 def _dir_hist_default(self):
426 426 try:
427 427 return [py3compat.getcwd()]
428 428 except OSError:
429 429 return []
430 430
431 431 # A dict of output history, keyed with ints from the shell's
432 432 # execution count.
433 433 output_hist = Dict()
434 434 # The text/plain repr of outputs.
435 435 output_hist_reprs = Dict()
436 436
437 437 # The number of the current session in the history database
438 438 session_number = Integer()
439 # Should we log output to the database? (default no)
440 db_log_output = Bool(False, config=True)
441 # Write to database every x commands (higher values save disk access & power)
442 # Values of 1 or less effectively disable caching.
443 db_cache_size = Integer(0, config=True)
439
440 db_log_output = Bool(False, config=True,
441 help="Should the history database include output? (default: no)"
442 )
443 db_cache_size = Integer(0, config=True,
444 help="Write to database every x commands (higher values save disk access & power).\n"
445 "Values of 1 or less effectively disable caching."
446 )
444 447 # The input and output caches
445 448 db_input_cache = List()
446 449 db_output_cache = List()
447 450
448 451 # History saving in separate thread
449 452 save_thread = Instance('IPython.core.history.HistorySavingThread')
450 453 try: # Event is a function returning an instance of _Event...
451 454 save_flag = Instance(threading._Event)
452 455 except AttributeError: # ...until Python 3.3, when it's a class.
453 456 save_flag = Instance(threading.Event)
454 457
455 458 # Private interface
456 459 # Variables used to store the three last inputs from the user. On each new
457 460 # history update, we populate the user's namespace with these, shifted as
458 461 # necessary.
459 462 _i00 = Unicode(u'')
460 463 _i = Unicode(u'')
461 464 _ii = Unicode(u'')
462 465 _iii = Unicode(u'')
463 466
464 467 # A regex matching all forms of the exit command, so that we don't store
465 468 # them in the history (it's annoying to rewind the first entry and land on
466 469 # an exit call).
467 470 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
468 471
469 472 def __init__(self, shell=None, config=None, **traits):
470 473 """Create a new history manager associated with a shell instance.
471 474 """
472 475 # We need a pointer back to the shell for various tasks.
473 476 super(HistoryManager, self).__init__(shell=shell, config=config,
474 477 **traits)
475 478 self.save_flag = threading.Event()
476 479 self.db_input_cache_lock = threading.Lock()
477 480 self.db_output_cache_lock = threading.Lock()
478 481 if self.enabled and self.hist_file != ':memory:':
479 482 self.save_thread = HistorySavingThread(self)
480 483 self.save_thread.start()
481 484
482 485 self.new_session()
483 486
484 487 def _get_hist_file_name(self, profile=None):
485 488 """Get default history file name based on the Shell's profile.
486 489
487 490 The profile parameter is ignored, but must exist for compatibility with
488 491 the parent class."""
489 492 profile_dir = self.shell.profile_dir.location
490 493 return os.path.join(profile_dir, 'history.sqlite')
491 494
492 495 @needs_sqlite
493 496 def new_session(self, conn=None):
494 497 """Get a new session number."""
495 498 if conn is None:
496 499 conn = self.db
497 500
498 501 with conn:
499 502 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
500 503 NULL, "") """, (datetime.datetime.now(),))
501 504 self.session_number = cur.lastrowid
502 505
503 506 def end_session(self):
504 507 """Close the database session, filling in the end time and line count."""
505 508 self.writeout_cache()
506 509 with self.db:
507 510 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
508 511 session==?""", (datetime.datetime.now(),
509 512 len(self.input_hist_parsed)-1, self.session_number))
510 513 self.session_number = 0
511 514
512 515 def name_session(self, name):
513 516 """Give the current session a name in the history database."""
514 517 with self.db:
515 518 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
516 519 (name, self.session_number))
517 520
518 521 def reset(self, new_session=True):
519 522 """Clear the session history, releasing all object references, and
520 523 optionally open a new session."""
521 524 self.output_hist.clear()
522 525 # The directory history can't be completely empty
523 526 self.dir_hist[:] = [py3compat.getcwd()]
524 527
525 528 if new_session:
526 529 if self.session_number:
527 530 self.end_session()
528 531 self.input_hist_parsed[:] = [""]
529 532 self.input_hist_raw[:] = [""]
530 533 self.new_session()
531 534
532 535 # ------------------------------
533 536 # Methods for retrieving history
534 537 # ------------------------------
535 538 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
536 539 """Get input and output history from the current session. Called by
537 540 get_range, and takes similar parameters."""
538 541 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
539 542
540 543 n = len(input_hist)
541 544 if start < 0:
542 545 start += n
543 546 if not stop or (stop > n):
544 547 stop = n
545 548 elif stop < 0:
546 549 stop += n
547 550
548 551 for i in range(start, stop):
549 552 if output:
550 553 line = (input_hist[i], self.output_hist_reprs.get(i))
551 554 else:
552 555 line = input_hist[i]
553 556 yield (0, i, line)
554 557
555 558 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
556 559 """Retrieve input by session.
557 560
558 561 Parameters
559 562 ----------
560 563 session : int
561 564 Session number to retrieve. The current session is 0, and negative
562 565 numbers count back from current session, so -1 is previous session.
563 566 start : int
564 567 First line to retrieve.
565 568 stop : int
566 569 End of line range (excluded from output itself). If None, retrieve
567 570 to the end of the session.
568 571 raw : bool
569 572 If True, return untranslated input
570 573 output : bool
571 574 If True, attempt to include output. This will be 'real' Python
572 575 objects for the current session, or text reprs from previous
573 576 sessions if db_log_output was enabled at the time. Where no output
574 577 is found, None is used.
575 578
576 579 Returns
577 580 -------
578 581 An iterator over the desired lines. Each line is a 3-tuple, either
579 582 (session, line, input) if output is False, or
580 583 (session, line, (input, output)) if output is True.
581 584 """
582 585 if session <= 0:
583 586 session += self.session_number
584 587 if session==self.session_number: # Current session
585 588 return self._get_range_session(start, stop, raw, output)
586 589 return super(HistoryManager, self).get_range(session, start, stop, raw,
587 590 output)
588 591
589 592 ## ----------------------------
590 593 ## Methods for storing history:
591 594 ## ----------------------------
592 595 def store_inputs(self, line_num, source, source_raw=None):
593 596 """Store source and raw input in history and create input cache
594 597 variables _i*.
595 598
596 599 Parameters
597 600 ----------
598 601 line_num : int
599 602 The prompt number of this input.
600 603
601 604 source : str
602 605 Python input.
603 606
604 607 source_raw : str, optional
605 608 If given, this is the raw input without any IPython transformations
606 609 applied to it. If not given, ``source`` is used.
607 610 """
608 611 if source_raw is None:
609 612 source_raw = source
610 613 source = source.rstrip('\n')
611 614 source_raw = source_raw.rstrip('\n')
612 615
613 616 # do not store exit/quit commands
614 617 if self._exit_re.match(source_raw.strip()):
615 618 return
616 619
617 620 self.input_hist_parsed.append(source)
618 621 self.input_hist_raw.append(source_raw)
619 622
620 623 with self.db_input_cache_lock:
621 624 self.db_input_cache.append((line_num, source, source_raw))
622 625 # Trigger to flush cache and write to DB.
623 626 if len(self.db_input_cache) >= self.db_cache_size:
624 627 self.save_flag.set()
625 628
626 629 # update the auto _i variables
627 630 self._iii = self._ii
628 631 self._ii = self._i
629 632 self._i = self._i00
630 633 self._i00 = source_raw
631 634
632 635 # hackish access to user namespace to create _i1,_i2... dynamically
633 636 new_i = '_i%s' % line_num
634 637 to_main = {'_i': self._i,
635 638 '_ii': self._ii,
636 639 '_iii': self._iii,
637 640 new_i : self._i00 }
638 641
639 642 if self.shell is not None:
640 643 self.shell.push(to_main, interactive=False)
641 644
642 645 def store_output(self, line_num):
643 646 """If database output logging is enabled, this saves all the
644 647 outputs from the indicated prompt number to the database. It's
645 648 called by run_cell after code has been executed.
646 649
647 650 Parameters
648 651 ----------
649 652 line_num : int
650 653 The line number from which to save outputs
651 654 """
652 655 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
653 656 return
654 657 output = self.output_hist_reprs[line_num]
655 658
656 659 with self.db_output_cache_lock:
657 660 self.db_output_cache.append((line_num, output))
658 661 if self.db_cache_size <= 1:
659 662 self.save_flag.set()
660 663
661 664 def _writeout_input_cache(self, conn):
662 665 with conn:
663 666 for line in self.db_input_cache:
664 667 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
665 668 (self.session_number,)+line)
666 669
667 670 def _writeout_output_cache(self, conn):
668 671 with conn:
669 672 for line in self.db_output_cache:
670 673 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
671 674 (self.session_number,)+line)
672 675
673 676 @needs_sqlite
674 677 def writeout_cache(self, conn=None):
675 678 """Write any entries in the cache to the database."""
676 679 if conn is None:
677 680 conn = self.db
678 681
679 682 with self.db_input_cache_lock:
680 683 try:
681 684 self._writeout_input_cache(conn)
682 685 except sqlite3.IntegrityError:
683 686 self.new_session(conn)
684 687 print("ERROR! Session/line number was not unique in",
685 688 "database. History logging moved to new session",
686 689 self.session_number)
687 690 try:
688 691 # Try writing to the new session. If this fails, don't
689 692 # recurse
690 693 self._writeout_input_cache(conn)
691 694 except sqlite3.IntegrityError:
692 695 pass
693 696 finally:
694 697 self.db_input_cache = []
695 698
696 699 with self.db_output_cache_lock:
697 700 try:
698 701 self._writeout_output_cache(conn)
699 702 except sqlite3.IntegrityError:
700 703 print("!! Session/line number for output was not unique",
701 704 "in database. Output will not be stored.")
702 705 finally:
703 706 self.db_output_cache = []
704 707
705 708
706 709 class HistorySavingThread(threading.Thread):
707 710 """This thread takes care of writing history to the database, so that
708 711 the UI isn't held up while that happens.
709 712
710 713 It waits for the HistoryManager's save_flag to be set, then writes out
711 714 the history cache. The main thread is responsible for setting the flag when
712 715 the cache size reaches a defined threshold."""
713 716 daemon = True
714 717 stop_now = False
715 718 enabled = True
716 719 def __init__(self, history_manager):
717 720 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
718 721 self.history_manager = history_manager
719 722 self.enabled = history_manager.enabled
720 723 atexit.register(self.stop)
721 724
722 725 @needs_sqlite
723 726 def run(self):
724 727 # We need a separate db connection per thread:
725 728 try:
726 729 self.db = sqlite3.connect(self.history_manager.hist_file,
727 730 **self.history_manager.connection_options
728 731 )
729 732 while True:
730 733 self.history_manager.save_flag.wait()
731 734 if self.stop_now:
732 735 return
733 736 self.history_manager.save_flag.clear()
734 737 self.history_manager.writeout_cache(self.db)
735 738 except Exception as e:
736 739 print(("The history saving thread hit an unexpected error (%s)."
737 740 "History will not be written to the database.") % repr(e))
738 741
739 742 def stop(self):
740 743 """This can be called from the main thread to safely stop this thread.
741 744
742 745 Note that it does not attempt to write out remaining history before
743 746 exiting. That should be done by calling the HistoryManager's
744 747 end_session method."""
745 748 self.stop_now = True
746 749 self.history_manager.save_flag.set()
747 750 self.join()
748 751
749 752
750 753 # To match, e.g. ~5/8-~2/3
751 754 range_re = re.compile(r"""
752 755 ((?P<startsess>~?\d+)/)?
753 756 (?P<start>\d+)?
754 757 ((?P<sep>[\-:])
755 758 ((?P<endsess>~?\d+)/)?
756 759 (?P<end>\d+))?
757 760 $""", re.VERBOSE)
758 761
759 762
760 763 def extract_hist_ranges(ranges_str):
761 764 """Turn a string of history ranges into 3-tuples of (session, start, stop).
762 765
763 766 Examples
764 767 --------
765 768 list(extract_input_ranges("~8/5-~7/4 2"))
766 769 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
767 770 """
768 771 for range_str in ranges_str.split():
769 772 rmatch = range_re.match(range_str)
770 773 if not rmatch:
771 774 continue
772 775 start = rmatch.group("start")
773 776 if start:
774 777 start = int(start)
775 778 end = rmatch.group("end")
776 779 # If no end specified, get (a, a + 1)
777 780 end = int(end) if end else start + 1
778 781 else: # start not specified
779 782 if not rmatch.group('startsess'): # no startsess
780 783 continue
781 784 start = 1
782 785 end = None # provide the entire session hist
783 786
784 787 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
785 788 end += 1
786 789 startsess = rmatch.group("startsess") or "0"
787 790 endsess = rmatch.group("endsess") or startsess
788 791 startsess = int(startsess.replace("~","-"))
789 792 endsess = int(endsess.replace("~","-"))
790 793 assert endsess >= startsess, "start session must be earlier than end session"
791 794
792 795 if endsess == startsess:
793 796 yield (startsess, start, end)
794 797 continue
795 798 # Multiple sessions in one range:
796 799 yield (startsess, start, None)
797 800 for sess in range(startsess+1, endsess):
798 801 yield (sess, 1, None)
799 802 yield (endsess, 1, end)
800 803
801 804
802 805 def _format_lineno(session, line):
803 806 """Helper function to format line numbers properly."""
804 807 if session == 0:
805 808 return str(line)
806 809 return "%s#%s" % (session, line)
807 810
808 811
General Comments 0
You need to be logged in to leave comments. Login now