##// END OF EJS Templates
Use single quotes in sql string literal
Audrey Dutcher -
Show More
@@ -1,965 +1,968 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 super(HistoryAccessor, self).__init__(**traits)
206 206 # defer setting hist_file from kwarg until after init,
207 207 # otherwise the default kwarg value would clobber any value
208 208 # set by config
209 209 if hist_file:
210 210 self.hist_file = hist_file
211 211
212 212 try:
213 213 self.hist_file
214 214 except TraitError:
215 215 # No one has set the hist_file, yet.
216 216 self.hist_file = self._get_hist_file_name(profile)
217 217
218 218 self.init_db()
219 219
220 220 def _get_hist_file_name(self, profile='default'):
221 221 """Find the history file for the given profile name.
222 222
223 223 This is overridden by the HistoryManager subclass, to use the shell's
224 224 active profile.
225 225
226 226 Parameters
227 227 ----------
228 228 profile : str
229 229 The name of a profile which has a history file.
230 230 """
231 231 return Path(locate_profile(profile)) / "history.sqlite"
232 232
233 233 @catch_corrupt_db
234 234 def init_db(self):
235 235 """Connect to the database, and create tables if necessary."""
236 236 if not self.enabled:
237 237 self.db = DummyDB()
238 238 return
239 239
240 240 # use detect_types so that timestamps return datetime objects
241 241 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
242 242 kwargs.update(self.connection_options)
243 243 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
244 244 with self.db:
245 245 self.db.execute(
246 246 """CREATE TABLE IF NOT EXISTS sessions (session integer
247 247 primary key autoincrement, start timestamp,
248 248 end timestamp, num_cmds integer, remark text)"""
249 249 )
250 250 self.db.execute(
251 251 """CREATE TABLE IF NOT EXISTS history
252 252 (session integer, line integer, source text, source_raw text,
253 253 PRIMARY KEY (session, line))"""
254 254 )
255 255 # Output history is optional, but ensure the table's there so it can be
256 256 # enabled later.
257 257 self.db.execute(
258 258 """CREATE TABLE IF NOT EXISTS output_history
259 259 (session integer, line integer, output text,
260 260 PRIMARY KEY (session, line))"""
261 261 )
262 262 # success! reset corrupt db count
263 263 self._corrupt_db_counter = 0
264 264
265 265 def writeout_cache(self):
266 266 """Overridden by HistoryManager to dump the cache before certain
267 267 database lookups."""
268 268 pass
269 269
270 270 ## -------------------------------
271 271 ## Methods for retrieving history:
272 272 ## -------------------------------
273 273 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
274 274 """Prepares and runs an SQL query for the history database.
275 275
276 276 Parameters
277 277 ----------
278 278 sql : str
279 279 Any filtering expressions to go after SELECT ... FROM ...
280 280 params : tuple
281 281 Parameters passed to the SQL query (to replace "?")
282 282 raw, output : bool
283 283 See :meth:`get_range`
284 284 latest : bool
285 285 Select rows with max (session, line)
286 286
287 287 Returns
288 288 -------
289 289 Tuples as :meth:`get_range`
290 290 """
291 291 toget = 'source_raw' if raw else 'source'
292 292 sqlfrom = "history"
293 293 if output:
294 294 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
295 295 toget = "history.%s, output_history.output" % toget
296 296 if latest:
297 297 toget += ", MAX(session * 128 * 1024 + line)"
298 298 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
299 299 cur = self.db.execute(this_querry, params)
300 300 if latest:
301 301 cur = (row[:-1] for row in cur)
302 302 if output: # Regroup into 3-tuples, and parse JSON
303 303 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
304 304 return cur
305 305
306 306 @only_when_enabled
307 307 @catch_corrupt_db
308 308 def get_session_info(self, session):
309 309 """Get info about a session.
310 310
311 311 Parameters
312 312 ----------
313 313 session : int
314 314 Session number to retrieve.
315 315
316 316 Returns
317 317 -------
318 318 session_id : int
319 319 Session ID number
320 320 start : datetime
321 321 Timestamp for the start of the session.
322 322 end : datetime
323 323 Timestamp for the end of the session, or None if IPython crashed.
324 324 num_cmds : int
325 325 Number of commands run, or None if IPython crashed.
326 326 remark : unicode
327 327 A manually set description.
328 328 """
329 329 query = "SELECT * from sessions where session == ?"
330 330 return self.db.execute(query, (session,)).fetchone()
331 331
332 332 @catch_corrupt_db
333 333 def get_last_session_id(self):
334 334 """Get the last session ID currently in the database.
335 335
336 336 Within IPython, this should be the same as the value stored in
337 337 :attr:`HistoryManager.session_number`.
338 338 """
339 339 for record in self.get_tail(n=1, include_latest=True):
340 340 return record[0]
341 341
342 342 @catch_corrupt_db
343 343 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
344 344 """Get the last n lines from the history database.
345 345
346 346 Parameters
347 347 ----------
348 348 n : int
349 349 The number of lines to get
350 350 raw, output : bool
351 351 See :meth:`get_range`
352 352 include_latest : bool
353 353 If False (default), n+1 lines are fetched, and the latest one
354 354 is discarded. This is intended to be used where the function
355 355 is called by a user command, which it should not return.
356 356
357 357 Returns
358 358 -------
359 359 Tuples as :meth:`get_range`
360 360 """
361 361 self.writeout_cache()
362 362 if not include_latest:
363 363 n += 1
364 364 cur = self._run_sql(
365 365 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
366 366 )
367 367 if not include_latest:
368 368 return reversed(list(cur)[1:])
369 369 return reversed(list(cur))
370 370
371 371 @catch_corrupt_db
372 372 def search(self, pattern="*", raw=True, search_raw=True,
373 373 output=False, n=None, unique=False):
374 374 """Search the database using unix glob-style matching (wildcards
375 375 * and ?).
376 376
377 377 Parameters
378 378 ----------
379 379 pattern : str
380 380 The wildcarded pattern to match when searching
381 381 search_raw : bool
382 382 If True, search the raw input, otherwise, the parsed input
383 383 raw, output : bool
384 384 See :meth:`get_range`
385 385 n : None or int
386 386 If an integer is given, it defines the limit of
387 387 returned entries.
388 388 unique : bool
389 389 When it is true, return only unique entries.
390 390
391 391 Returns
392 392 -------
393 393 Tuples as :meth:`get_range`
394 394 """
395 395 tosearch = "source_raw" if search_raw else "source"
396 396 if output:
397 397 tosearch = "history." + tosearch
398 398 self.writeout_cache()
399 399 sqlform = "WHERE %s GLOB ?" % tosearch
400 400 params = (pattern,)
401 401 if unique:
402 402 sqlform += ' GROUP BY {0}'.format(tosearch)
403 403 if n is not None:
404 404 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
405 405 params += (n,)
406 406 elif unique:
407 407 sqlform += " ORDER BY session, line"
408 408 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
409 409 if n is not None:
410 410 return reversed(list(cur))
411 411 return cur
412 412
413 413 @catch_corrupt_db
414 414 def get_range(self, session, start=1, stop=None, raw=True,output=False):
415 415 """Retrieve input by session.
416 416
417 417 Parameters
418 418 ----------
419 419 session : int
420 420 Session number to retrieve.
421 421 start : int
422 422 First line to retrieve.
423 423 stop : int
424 424 End of line range (excluded from output itself). If None, retrieve
425 425 to the end of the session.
426 426 raw : bool
427 427 If True, return untranslated input
428 428 output : bool
429 429 If True, attempt to include output. This will be 'real' Python
430 430 objects for the current session, or text reprs from previous
431 431 sessions if db_log_output was enabled at the time. Where no output
432 432 is found, None is used.
433 433
434 434 Returns
435 435 -------
436 436 entries
437 437 An iterator over the desired lines. Each line is a 3-tuple, either
438 438 (session, line, input) if output is False, or
439 439 (session, line, (input, output)) if output is True.
440 440 """
441 441 if stop:
442 442 lineclause = "line >= ? AND line < ?"
443 443 params = (session, start, stop)
444 444 else:
445 445 lineclause = "line>=?"
446 446 params = (session, start)
447 447
448 448 return self._run_sql("WHERE session==? AND %s" % lineclause,
449 449 params, raw=raw, output=output)
450 450
451 451 def get_range_by_str(self, rangestr, raw=True, output=False):
452 452 """Get lines of history from a string of ranges, as used by magic
453 453 commands %hist, %save, %macro, etc.
454 454
455 455 Parameters
456 456 ----------
457 457 rangestr : str
458 458 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
459 459 this will return everything from current session's history.
460 460
461 461 See the documentation of :func:`%history` for the full details.
462 462
463 463 raw, output : bool
464 464 As :meth:`get_range`
465 465
466 466 Returns
467 467 -------
468 468 Tuples as :meth:`get_range`
469 469 """
470 470 for sess, s, e in extract_hist_ranges(rangestr):
471 471 for line in self.get_range(sess, s, e, raw=raw, output=output):
472 472 yield line
473 473
474 474
475 475 class HistoryManager(HistoryAccessor):
476 476 """A class to organize all history-related functionality in one place.
477 477 """
478 478 # Public interface
479 479
480 480 # An instance of the IPython shell we are attached to
481 481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
482 482 allow_none=True)
483 483 # Lists to hold processed and raw history. These start with a blank entry
484 484 # so that we can index them starting from 1
485 485 input_hist_parsed = List([""])
486 486 input_hist_raw = List([""])
487 487 # A list of directories visited during session
488 488 dir_hist = List()
489 489 @default('dir_hist')
490 490 def _dir_hist_default(self):
491 491 try:
492 492 return [Path.cwd()]
493 493 except OSError:
494 494 return []
495 495
496 496 # A dict of output history, keyed with ints from the shell's
497 497 # execution count.
498 498 output_hist = Dict()
499 499 # The text/plain repr of outputs.
500 500 output_hist_reprs = Dict()
501 501
502 502 # The number of the current session in the history database
503 503 session_number = Integer()
504 504
505 505 db_log_output = Bool(False,
506 506 help="Should the history database include output? (default: no)"
507 507 ).tag(config=True)
508 508 db_cache_size = Integer(0,
509 509 help="Write to database every x commands (higher values save disk access & power).\n"
510 510 "Values of 1 or less effectively disable caching."
511 511 ).tag(config=True)
512 512 # The input and output caches
513 513 db_input_cache = List()
514 514 db_output_cache = List()
515 515
516 516 # History saving in separate thread
517 517 save_thread = Instance('IPython.core.history.HistorySavingThread',
518 518 allow_none=True)
519 519 save_flag = Instance(threading.Event, allow_none=True)
520 520
521 521 # Private interface
522 522 # Variables used to store the three last inputs from the user. On each new
523 523 # history update, we populate the user's namespace with these, shifted as
524 524 # necessary.
525 525 _i00 = Unicode(u'')
526 526 _i = Unicode(u'')
527 527 _ii = Unicode(u'')
528 528 _iii = Unicode(u'')
529 529
530 530 # A regex matching all forms of the exit command, so that we don't store
531 531 # them in the history (it's annoying to rewind the first entry and land on
532 532 # an exit call).
533 533 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
534 534
535 535 def __init__(self, shell=None, config=None, **traits):
536 536 """Create a new history manager associated with a shell instance.
537 537 """
538 538 super(HistoryManager, self).__init__(shell=shell, config=config,
539 539 **traits)
540 540 self.save_flag = threading.Event()
541 541 self.db_input_cache_lock = threading.Lock()
542 542 self.db_output_cache_lock = threading.Lock()
543 543
544 544 try:
545 545 self.new_session()
546 546 except sqlite3.OperationalError:
547 547 self.log.error("Failed to create history session in %s. History will not be saved.",
548 548 self.hist_file, exc_info=True)
549 549 self.hist_file = ':memory:'
550 550
551 551 if self.enabled and self.hist_file != ':memory:':
552 552 self.save_thread = HistorySavingThread(self)
553 553 self.save_thread.start()
554 554
555 555 def _get_hist_file_name(self, profile=None):
556 556 """Get default history file name based on the Shell's profile.
557 557
558 558 The profile parameter is ignored, but must exist for compatibility with
559 559 the parent class."""
560 560 profile_dir = self.shell.profile_dir.location
561 561 return Path(profile_dir) / "history.sqlite"
562 562
563 563 @only_when_enabled
564 564 def new_session(self, conn=None):
565 565 """Get a new session number."""
566 566 if conn is None:
567 567 conn = self.db
568 568
569 569 with conn:
570 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
571 NULL, "") """, (datetime.datetime.now(),))
570 cur = conn.execute(
571 """INSERT INTO sessions VALUES (NULL, ?, NULL,
572 NULL, '') """,
573 (datetime.datetime.now(),),
574 )
572 575 self.session_number = cur.lastrowid
573 576
574 577 def end_session(self):
575 578 """Close the database session, filling in the end time and line count."""
576 579 self.writeout_cache()
577 580 with self.db:
578 581 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
579 582 session==?""", (datetime.datetime.now(),
580 583 len(self.input_hist_parsed)-1, self.session_number))
581 584 self.session_number = 0
582 585
583 586 def name_session(self, name):
584 587 """Give the current session a name in the history database."""
585 588 with self.db:
586 589 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
587 590 (name, self.session_number))
588 591
589 592 def reset(self, new_session=True):
590 593 """Clear the session history, releasing all object references, and
591 594 optionally open a new session."""
592 595 self.output_hist.clear()
593 596 # The directory history can't be completely empty
594 597 self.dir_hist[:] = [Path.cwd()]
595 598
596 599 if new_session:
597 600 if self.session_number:
598 601 self.end_session()
599 602 self.input_hist_parsed[:] = [""]
600 603 self.input_hist_raw[:] = [""]
601 604 self.new_session()
602 605
603 606 # ------------------------------
604 607 # Methods for retrieving history
605 608 # ------------------------------
606 609 def get_session_info(self, session=0):
607 610 """Get info about a session.
608 611
609 612 Parameters
610 613 ----------
611 614 session : int
612 615 Session number to retrieve. The current session is 0, and negative
613 616 numbers count back from current session, so -1 is the previous session.
614 617
615 618 Returns
616 619 -------
617 620 session_id : int
618 621 Session ID number
619 622 start : datetime
620 623 Timestamp for the start of the session.
621 624 end : datetime
622 625 Timestamp for the end of the session, or None if IPython crashed.
623 626 num_cmds : int
624 627 Number of commands run, or None if IPython crashed.
625 628 remark : unicode
626 629 A manually set description.
627 630 """
628 631 if session <= 0:
629 632 session += self.session_number
630 633
631 634 return super(HistoryManager, self).get_session_info(session=session)
632 635
633 636 @catch_corrupt_db
634 637 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
635 638 """Get the last n lines from the history database.
636 639
637 640 Most recent entry last.
638 641
639 642 Completion will be reordered so that that the last ones are when
640 643 possible from current session.
641 644
642 645 Parameters
643 646 ----------
644 647 n : int
645 648 The number of lines to get
646 649 raw, output : bool
647 650 See :meth:`get_range`
648 651 include_latest : bool
649 652 If False (default), n+1 lines are fetched, and the latest one
650 653 is discarded. This is intended to be used where the function
651 654 is called by a user command, which it should not return.
652 655
653 656 Returns
654 657 -------
655 658 Tuples as :meth:`get_range`
656 659 """
657 660 self.writeout_cache()
658 661 if not include_latest:
659 662 n += 1
660 663 # cursor/line/entry
661 664 this_cur = list(
662 665 self._run_sql(
663 666 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
664 667 (self.session_number, n),
665 668 raw=raw,
666 669 output=output,
667 670 )
668 671 )
669 672 other_cur = list(
670 673 self._run_sql(
671 674 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
672 675 (self.session_number, n),
673 676 raw=raw,
674 677 output=output,
675 678 )
676 679 )
677 680
678 681 everything = this_cur + other_cur
679 682
680 683 everything = everything[:n]
681 684
682 685 if not include_latest:
683 686 return list(everything)[:0:-1]
684 687 return list(everything)[::-1]
685 688
686 689 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
687 690 """Get input and output history from the current session. Called by
688 691 get_range, and takes similar parameters."""
689 692 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
690 693
691 694 n = len(input_hist)
692 695 if start < 0:
693 696 start += n
694 697 if not stop or (stop > n):
695 698 stop = n
696 699 elif stop < 0:
697 700 stop += n
698 701
699 702 for i in range(start, stop):
700 703 if output:
701 704 line = (input_hist[i], self.output_hist_reprs.get(i))
702 705 else:
703 706 line = input_hist[i]
704 707 yield (0, i, line)
705 708
706 709 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
707 710 """Retrieve input by session.
708 711
709 712 Parameters
710 713 ----------
711 714 session : int
712 715 Session number to retrieve. The current session is 0, and negative
713 716 numbers count back from current session, so -1 is previous session.
714 717 start : int
715 718 First line to retrieve.
716 719 stop : int
717 720 End of line range (excluded from output itself). If None, retrieve
718 721 to the end of the session.
719 722 raw : bool
720 723 If True, return untranslated input
721 724 output : bool
722 725 If True, attempt to include output. This will be 'real' Python
723 726 objects for the current session, or text reprs from previous
724 727 sessions if db_log_output was enabled at the time. Where no output
725 728 is found, None is used.
726 729
727 730 Returns
728 731 -------
729 732 entries
730 733 An iterator over the desired lines. Each line is a 3-tuple, either
731 734 (session, line, input) if output is False, or
732 735 (session, line, (input, output)) if output is True.
733 736 """
734 737 if session <= 0:
735 738 session += self.session_number
736 739 if session==self.session_number: # Current session
737 740 return self._get_range_session(start, stop, raw, output)
738 741 return super(HistoryManager, self).get_range(session, start, stop, raw,
739 742 output)
740 743
741 744 ## ----------------------------
742 745 ## Methods for storing history:
743 746 ## ----------------------------
744 747 def store_inputs(self, line_num, source, source_raw=None):
745 748 """Store source and raw input in history and create input cache
746 749 variables ``_i*``.
747 750
748 751 Parameters
749 752 ----------
750 753 line_num : int
751 754 The prompt number of this input.
752 755 source : str
753 756 Python input.
754 757 source_raw : str, optional
755 758 If given, this is the raw input without any IPython transformations
756 759 applied to it. If not given, ``source`` is used.
757 760 """
758 761 if source_raw is None:
759 762 source_raw = source
760 763 source = source.rstrip('\n')
761 764 source_raw = source_raw.rstrip('\n')
762 765
763 766 # do not store exit/quit commands
764 767 if self._exit_re.match(source_raw.strip()):
765 768 return
766 769
767 770 self.input_hist_parsed.append(source)
768 771 self.input_hist_raw.append(source_raw)
769 772
770 773 with self.db_input_cache_lock:
771 774 self.db_input_cache.append((line_num, source, source_raw))
772 775 # Trigger to flush cache and write to DB.
773 776 if len(self.db_input_cache) >= self.db_cache_size:
774 777 self.save_flag.set()
775 778
776 779 # update the auto _i variables
777 780 self._iii = self._ii
778 781 self._ii = self._i
779 782 self._i = self._i00
780 783 self._i00 = source_raw
781 784
782 785 # hackish access to user namespace to create _i1,_i2... dynamically
783 786 new_i = '_i%s' % line_num
784 787 to_main = {'_i': self._i,
785 788 '_ii': self._ii,
786 789 '_iii': self._iii,
787 790 new_i : self._i00 }
788 791
789 792 if self.shell is not None:
790 793 self.shell.push(to_main, interactive=False)
791 794
792 795 def store_output(self, line_num):
793 796 """If database output logging is enabled, this saves all the
794 797 outputs from the indicated prompt number to the database. It's
795 798 called by run_cell after code has been executed.
796 799
797 800 Parameters
798 801 ----------
799 802 line_num : int
800 803 The line number from which to save outputs
801 804 """
802 805 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
803 806 return
804 807 output = self.output_hist_reprs[line_num]
805 808
806 809 with self.db_output_cache_lock:
807 810 self.db_output_cache.append((line_num, output))
808 811 if self.db_cache_size <= 1:
809 812 self.save_flag.set()
810 813
811 814 def _writeout_input_cache(self, conn):
812 815 with conn:
813 816 for line in self.db_input_cache:
814 817 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
815 818 (self.session_number,)+line)
816 819
817 820 def _writeout_output_cache(self, conn):
818 821 with conn:
819 822 for line in self.db_output_cache:
820 823 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
821 824 (self.session_number,)+line)
822 825
823 826 @only_when_enabled
824 827 def writeout_cache(self, conn=None):
825 828 """Write any entries in the cache to the database."""
826 829 if conn is None:
827 830 conn = self.db
828 831
829 832 with self.db_input_cache_lock:
830 833 try:
831 834 self._writeout_input_cache(conn)
832 835 except sqlite3.IntegrityError:
833 836 self.new_session(conn)
834 837 print("ERROR! Session/line number was not unique in",
835 838 "database. History logging moved to new session",
836 839 self.session_number)
837 840 try:
838 841 # Try writing to the new session. If this fails, don't
839 842 # recurse
840 843 self._writeout_input_cache(conn)
841 844 except sqlite3.IntegrityError:
842 845 pass
843 846 finally:
844 847 self.db_input_cache = []
845 848
846 849 with self.db_output_cache_lock:
847 850 try:
848 851 self._writeout_output_cache(conn)
849 852 except sqlite3.IntegrityError:
850 853 print("!! Session/line number for output was not unique",
851 854 "in database. Output will not be stored.")
852 855 finally:
853 856 self.db_output_cache = []
854 857
855 858
856 859 class HistorySavingThread(threading.Thread):
857 860 """This thread takes care of writing history to the database, so that
858 861 the UI isn't held up while that happens.
859 862
860 863 It waits for the HistoryManager's save_flag to be set, then writes out
861 864 the history cache. The main thread is responsible for setting the flag when
862 865 the cache size reaches a defined threshold."""
863 866 daemon = True
864 867 stop_now = False
865 868 enabled = True
866 869 def __init__(self, history_manager):
867 870 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
868 871 self.history_manager = history_manager
869 872 self.enabled = history_manager.enabled
870 873 atexit.register(self.stop)
871 874
872 875 @only_when_enabled
873 876 def run(self):
874 877 # We need a separate db connection per thread:
875 878 try:
876 879 self.db = sqlite3.connect(
877 880 str(self.history_manager.hist_file),
878 881 **self.history_manager.connection_options,
879 882 )
880 883 while True:
881 884 self.history_manager.save_flag.wait()
882 885 if self.stop_now:
883 886 self.db.close()
884 887 return
885 888 self.history_manager.save_flag.clear()
886 889 self.history_manager.writeout_cache(self.db)
887 890 except Exception as e:
888 891 print(("The history saving thread hit an unexpected error (%s)."
889 892 "History will not be written to the database.") % repr(e))
890 893
891 894 def stop(self):
892 895 """This can be called from the main thread to safely stop this thread.
893 896
894 897 Note that it does not attempt to write out remaining history before
895 898 exiting. That should be done by calling the HistoryManager's
896 899 end_session method."""
897 900 self.stop_now = True
898 901 self.history_manager.save_flag.set()
899 902 self.join()
900 903
901 904
902 905 # To match, e.g. ~5/8-~2/3
903 906 range_re = re.compile(r"""
904 907 ((?P<startsess>~?\d+)/)?
905 908 (?P<start>\d+)?
906 909 ((?P<sep>[\-:])
907 910 ((?P<endsess>~?\d+)/)?
908 911 (?P<end>\d+))?
909 912 $""", re.VERBOSE)
910 913
911 914
912 915 def extract_hist_ranges(ranges_str):
913 916 """Turn a string of history ranges into 3-tuples of (session, start, stop).
914 917
915 918 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
916 919 session".
917 920
918 921 Examples
919 922 --------
920 923 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
921 924 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
922 925 """
923 926 if ranges_str == "":
924 927 yield (0, 1, None) # Everything from current session
925 928 return
926 929
927 930 for range_str in ranges_str.split():
928 931 rmatch = range_re.match(range_str)
929 932 if not rmatch:
930 933 continue
931 934 start = rmatch.group("start")
932 935 if start:
933 936 start = int(start)
934 937 end = rmatch.group("end")
935 938 # If no end specified, get (a, a + 1)
936 939 end = int(end) if end else start + 1
937 940 else: # start not specified
938 941 if not rmatch.group('startsess'): # no startsess
939 942 continue
940 943 start = 1
941 944 end = None # provide the entire session hist
942 945
943 946 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
944 947 end += 1
945 948 startsess = rmatch.group("startsess") or "0"
946 949 endsess = rmatch.group("endsess") or startsess
947 950 startsess = int(startsess.replace("~","-"))
948 951 endsess = int(endsess.replace("~","-"))
949 952 assert endsess >= startsess, "start session must be earlier than end session"
950 953
951 954 if endsess == startsess:
952 955 yield (startsess, start, end)
953 956 continue
954 957 # Multiple sessions in one range:
955 958 yield (startsess, start, None)
956 959 for sess in range(startsess+1, endsess):
957 960 yield (sess, 1, None)
958 961 yield (endsess, 1, end)
959 962
960 963
961 964 def _format_lineno(session, line):
962 965 """Helper function to format line numbers properly."""
963 966 if session == 0:
964 967 return str(line)
965 968 return "%s#%s" % (session, line)
General Comments 0
You need to be logged in to leave comments. Login now