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