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