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