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