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