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