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