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