Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/core/history.py: 27%

388 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

1""" History related magics and functionality """ 

2 

3# Copyright (c) IPython Development Team. 

4# Distributed under the terms of the Modified BSD License. 

5 

6 

7import atexit 

8import datetime 

9from pathlib import Path 

10import re 

11import sqlite3 

12import threading 

13 

14from traitlets.config.configurable import LoggingConfigurable 

15from decorator import decorator 

16from IPython.utils.decorators import undoc 

17from IPython.paths import locate_profile 

18from traitlets import ( 

19 Any, 

20 Bool, 

21 Dict, 

22 Instance, 

23 Integer, 

24 List, 

25 Unicode, 

26 Union, 

27 TraitError, 

28 default, 

29 observe, 

30) 

31 

32#----------------------------------------------------------------------------- 

33# Classes and functions 

34#----------------------------------------------------------------------------- 

35 

36@undoc 

37class DummyDB(object): 

38 """Dummy DB that will act as a black hole for history. 

39 

40 Only used in the absence of sqlite""" 

41 def execute(*args, **kwargs): 

42 return [] 

43 

44 def commit(self, *args, **kwargs): 

45 pass 

46 

47 def __enter__(self, *args, **kwargs): 

48 pass 

49 

50 def __exit__(self, *args, **kwargs): 

51 pass 

52 

53 

54@decorator 

55def only_when_enabled(f, self, *a, **kw): 

56 """Decorator: return an empty list in the absence of sqlite.""" 

57 if not self.enabled: 

58 return [] 

59 else: 

60 return f(self, *a, **kw) 

61 

62 

63# use 16kB as threshold for whether a corrupt history db should be saved 

64# that should be at least 100 entries or so 

65_SAVE_DB_SIZE = 16384 

66 

67@decorator 

68def catch_corrupt_db(f, self, *a, **kw): 

69 """A decorator which wraps HistoryAccessor method calls to catch errors from 

70 a corrupt SQLite database, move the old database out of the way, and create 

71 a new one. 

72 

73 We avoid clobbering larger databases because this may be triggered due to filesystem issues, 

74 not just a corrupt file. 

75 """ 

76 try: 

77 return f(self, *a, **kw) 

78 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e: 

79 self._corrupt_db_counter += 1 

80 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e) 

81 if self.hist_file != ':memory:': 

82 if self._corrupt_db_counter > self._corrupt_db_limit: 

83 self.hist_file = ':memory:' 

84 self.log.error("Failed to load history too many times, history will not be saved.") 

85 elif self.hist_file.is_file(): 

86 # move the file out of the way 

87 base = str(self.hist_file.parent / self.hist_file.stem) 

88 ext = self.hist_file.suffix 

89 size = self.hist_file.stat().st_size 

90 if size >= _SAVE_DB_SIZE: 

91 # if there's significant content, avoid clobbering 

92 now = datetime.datetime.now().isoformat().replace(':', '.') 

93 newpath = base + '-corrupt-' + now + ext 

94 # don't clobber previous corrupt backups 

95 for i in range(100): 

96 if not Path(newpath).exists(): 

97 break 

98 else: 

99 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext 

100 else: 

101 # not much content, possibly empty; don't worry about clobbering 

102 # maybe we should just delete it? 

103 newpath = base + '-corrupt' + ext 

104 self.hist_file.rename(newpath) 

105 self.log.error("History file was moved to %s and a new file created.", newpath) 

106 self.init_db() 

107 return [] 

108 else: 

109 # Failed with :memory:, something serious is wrong 

110 raise 

111 

112 

113class HistoryAccessorBase(LoggingConfigurable): 

114 """An abstract class for History Accessors """ 

115 

116 def get_tail(self, n=10, raw=True, output=False, include_latest=False): 

117 raise NotImplementedError 

118 

119 def search(self, pattern="*", raw=True, search_raw=True, 

120 output=False, n=None, unique=False): 

121 raise NotImplementedError 

122 

123 def get_range(self, session, start=1, stop=None, raw=True,output=False): 

124 raise NotImplementedError 

125 

126 def get_range_by_str(self, rangestr, raw=True, output=False): 

127 raise NotImplementedError 

128 

129 

130class HistoryAccessor(HistoryAccessorBase): 

131 """Access the history database without adding to it. 

132 

133 This is intended for use by standalone history tools. IPython shells use 

134 HistoryManager, below, which is a subclass of this.""" 

135 

136 # counter for init_db retries, so we don't keep trying over and over 

137 _corrupt_db_counter = 0 

138 # after two failures, fallback on :memory: 

139 _corrupt_db_limit = 2 

140 

141 # String holding the path to the history file 

142 hist_file = Union( 

143 [Instance(Path), Unicode()], 

144 help="""Path to file to use for SQLite history database. 

145 

146 By default, IPython will put the history database in the IPython 

147 profile directory. If you would rather share one history among 

148 profiles, you can set this value in each, so that they are consistent. 

149 

150 Due to an issue with fcntl, SQLite is known to misbehave on some NFS 

151 mounts. If you see IPython hanging, try setting this to something on a 

152 local disk, e.g:: 

153 

154 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite 

155 

156 you can also use the specific value `:memory:` (including the colon 

157 at both end but not the back ticks), to avoid creating an history file. 

158 

159 """, 

160 ).tag(config=True) 

161 

162 enabled = Bool(True, 

163 help="""enable the SQLite history 

164 

165 set enabled=False to disable the SQLite history, 

166 in which case there will be no stored history, no SQLite connection, 

167 and no background saving thread. This may be necessary in some 

168 threaded environments where IPython is embedded. 

169 """, 

170 ).tag(config=True) 

171 

172 connection_options = Dict( 

173 help="""Options for configuring the SQLite connection 

174 

175 These options are passed as keyword args to sqlite3.connect 

176 when establishing database connections. 

177 """ 

178 ).tag(config=True) 

179 

180 # The SQLite database 

181 db = Any() 

182 @observe('db') 

183 def _db_changed(self, change): 

184 """validate the db, since it can be an Instance of two different types""" 

185 new = change['new'] 

186 connection_types = (DummyDB, sqlite3.Connection) 

187 if not isinstance(new, connection_types): 

188 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ 

189 (self.__class__.__name__, new) 

190 raise TraitError(msg) 

191 

192 def __init__(self, profile="default", hist_file="", **traits): 

193 """Create a new history accessor. 

194 

195 Parameters 

196 ---------- 

197 profile : str 

198 The name of the profile from which to open history. 

199 hist_file : str 

200 Path to an SQLite history database stored by IPython. If specified, 

201 hist_file overrides profile. 

202 config : :class:`~traitlets.config.loader.Config` 

203 Config object. hist_file can also be set through this. 

204 """ 

205 super(HistoryAccessor, self).__init__(**traits) 

206 # defer setting hist_file from kwarg until after init, 

207 # otherwise the default kwarg value would clobber any value 

208 # set by config 

209 if hist_file: 

210 self.hist_file = hist_file 

211 

212 try: 

213 self.hist_file 

214 except TraitError: 

215 # No one has set the hist_file, yet. 

216 self.hist_file = self._get_hist_file_name(profile) 

217 

218 self.init_db() 

219 

220 def _get_hist_file_name(self, profile='default'): 

221 """Find the history file for the given profile name. 

222 

223 This is overridden by the HistoryManager subclass, to use the shell's 

224 active profile. 

225 

226 Parameters 

227 ---------- 

228 profile : str 

229 The name of a profile which has a history file. 

230 """ 

231 return Path(locate_profile(profile)) / "history.sqlite" 

232 

233 @catch_corrupt_db 

234 def init_db(self): 

235 """Connect to the database, and create tables if necessary.""" 

236 if not self.enabled: 

237 self.db = DummyDB() 

238 return 

239 

240 # use detect_types so that timestamps return datetime objects 

241 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) 

242 kwargs.update(self.connection_options) 

243 self.db = sqlite3.connect(str(self.hist_file), **kwargs) 

244 with self.db: 

245 self.db.execute( 

246 """CREATE TABLE IF NOT EXISTS sessions (session integer 

247 primary key autoincrement, start timestamp, 

248 end timestamp, num_cmds integer, remark text)""" 

249 ) 

250 self.db.execute( 

251 """CREATE TABLE IF NOT EXISTS history 

252 (session integer, line integer, source text, source_raw text, 

253 PRIMARY KEY (session, line))""" 

254 ) 

255 # Output history is optional, but ensure the table's there so it can be 

256 # enabled later. 

257 self.db.execute( 

258 """CREATE TABLE IF NOT EXISTS output_history 

259 (session integer, line integer, output text, 

260 PRIMARY KEY (session, line))""" 

261 ) 

262 # success! reset corrupt db count 

263 self._corrupt_db_counter = 0 

264 

265 def writeout_cache(self): 

266 """Overridden by HistoryManager to dump the cache before certain 

267 database lookups.""" 

268 pass 

269 

270 ## ------------------------------- 

271 ## Methods for retrieving history: 

272 ## ------------------------------- 

273 def _run_sql(self, sql, params, raw=True, output=False, latest=False): 

274 """Prepares and runs an SQL query for the history database. 

275 

276 Parameters 

277 ---------- 

278 sql : str 

279 Any filtering expressions to go after SELECT ... FROM ... 

280 params : tuple 

281 Parameters passed to the SQL query (to replace "?") 

282 raw, output : bool 

283 See :meth:`get_range` 

284 latest : bool 

285 Select rows with max (session, line) 

286 

287 Returns 

288 ------- 

289 Tuples as :meth:`get_range` 

290 """ 

291 toget = 'source_raw' if raw else 'source' 

292 sqlfrom = "history" 

293 if output: 

294 sqlfrom = "history LEFT JOIN output_history USING (session, line)" 

295 toget = "history.%s, output_history.output" % toget 

296 if latest: 

297 toget += ", MAX(session * 128 * 1024 + line)" 

298 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql 

299 cur = self.db.execute(this_querry, params) 

300 if latest: 

301 cur = (row[:-1] for row in cur) 

302 if output: # Regroup into 3-tuples, and parse JSON 

303 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) 

304 return cur 

305 

306 @only_when_enabled 

307 @catch_corrupt_db 

308 def get_session_info(self, session): 

309 """Get info about a session. 

310 

311 Parameters 

312 ---------- 

313 session : int 

314 Session number to retrieve. 

315 

316 Returns 

317 ------- 

318 session_id : int 

319 Session ID number 

320 start : datetime 

321 Timestamp for the start of the session. 

322 end : datetime 

323 Timestamp for the end of the session, or None if IPython crashed. 

324 num_cmds : int 

325 Number of commands run, or None if IPython crashed. 

326 remark : unicode 

327 A manually set description. 

328 """ 

329 query = "SELECT * from sessions where session == ?" 

330 return self.db.execute(query, (session,)).fetchone() 

331 

332 @catch_corrupt_db 

333 def get_last_session_id(self): 

334 """Get the last session ID currently in the database. 

335 

336 Within IPython, this should be the same as the value stored in 

337 :attr:`HistoryManager.session_number`. 

338 """ 

339 for record in self.get_tail(n=1, include_latest=True): 

340 return record[0] 

341 

342 @catch_corrupt_db 

343 def get_tail(self, n=10, raw=True, output=False, include_latest=False): 

344 """Get the last n lines from the history database. 

345 

346 Parameters 

347 ---------- 

348 n : int 

349 The number of lines to get 

350 raw, output : bool 

351 See :meth:`get_range` 

352 include_latest : bool 

353 If False (default), n+1 lines are fetched, and the latest one 

354 is discarded. This is intended to be used where the function 

355 is called by a user command, which it should not return. 

356 

357 Returns 

358 ------- 

359 Tuples as :meth:`get_range` 

360 """ 

361 self.writeout_cache() 

362 if not include_latest: 

363 n += 1 

364 cur = self._run_sql( 

365 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output 

366 ) 

367 if not include_latest: 

368 return reversed(list(cur)[1:]) 

369 return reversed(list(cur)) 

370 

371 @catch_corrupt_db 

372 def search(self, pattern="*", raw=True, search_raw=True, 

373 output=False, n=None, unique=False): 

374 """Search the database using unix glob-style matching (wildcards 

375 * and ?). 

376 

377 Parameters 

378 ---------- 

379 pattern : str 

380 The wildcarded pattern to match when searching 

381 search_raw : bool 

382 If True, search the raw input, otherwise, the parsed input 

383 raw, output : bool 

384 See :meth:`get_range` 

385 n : None or int 

386 If an integer is given, it defines the limit of 

387 returned entries. 

388 unique : bool 

389 When it is true, return only unique entries. 

390 

391 Returns 

392 ------- 

393 Tuples as :meth:`get_range` 

394 """ 

395 tosearch = "source_raw" if search_raw else "source" 

396 if output: 

397 tosearch = "history." + tosearch 

398 self.writeout_cache() 

399 sqlform = "WHERE %s GLOB ?" % tosearch 

400 params = (pattern,) 

401 if unique: 

402 sqlform += ' GROUP BY {0}'.format(tosearch) 

403 if n is not None: 

404 sqlform += " ORDER BY session DESC, line DESC LIMIT ?" 

405 params += (n,) 

406 elif unique: 

407 sqlform += " ORDER BY session, line" 

408 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique) 

409 if n is not None: 

410 return reversed(list(cur)) 

411 return cur 

412 

413 @catch_corrupt_db 

414 def get_range(self, session, start=1, stop=None, raw=True,output=False): 

415 """Retrieve input by session. 

416 

417 Parameters 

418 ---------- 

419 session : int 

420 Session number to retrieve. 

421 start : int 

422 First line to retrieve. 

423 stop : int 

424 End of line range (excluded from output itself). If None, retrieve 

425 to the end of the session. 

426 raw : bool 

427 If True, return untranslated input 

428 output : bool 

429 If True, attempt to include output. This will be 'real' Python 

430 objects for the current session, or text reprs from previous 

431 sessions if db_log_output was enabled at the time. Where no output 

432 is found, None is used. 

433 

434 Returns 

435 ------- 

436 entries 

437 An iterator over the desired lines. Each line is a 3-tuple, either 

438 (session, line, input) if output is False, or 

439 (session, line, (input, output)) if output is True. 

440 """ 

441 if stop: 

442 lineclause = "line >= ? AND line < ?" 

443 params = (session, start, stop) 

444 else: 

445 lineclause = "line>=?" 

446 params = (session, start) 

447 

448 return self._run_sql("WHERE session==? AND %s" % lineclause, 

449 params, raw=raw, output=output) 

450 

451 def get_range_by_str(self, rangestr, raw=True, output=False): 

452 """Get lines of history from a string of ranges, as used by magic 

453 commands %hist, %save, %macro, etc. 

454 

455 Parameters 

456 ---------- 

457 rangestr : str 

458 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used, 

459 this will return everything from current session's history. 

460 

461 See the documentation of :func:`%history` for the full details. 

462 

463 raw, output : bool 

464 As :meth:`get_range` 

465 

466 Returns 

467 ------- 

468 Tuples as :meth:`get_range` 

469 """ 

470 for sess, s, e in extract_hist_ranges(rangestr): 

471 for line in self.get_range(sess, s, e, raw=raw, output=output): 

472 yield line 

473 

474 

475class HistoryManager(HistoryAccessor): 

476 """A class to organize all history-related functionality in one place. 

477 """ 

478 # Public interface 

479 

480 # An instance of the IPython shell we are attached to 

481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', 

482 allow_none=True) 

483 # Lists to hold processed and raw history. These start with a blank entry 

484 # so that we can index them starting from 1 

485 input_hist_parsed = List([""]) 

486 input_hist_raw = List([""]) 

487 # A list of directories visited during session 

488 dir_hist = List() 

489 @default('dir_hist') 

490 def _dir_hist_default(self): 

491 try: 

492 return [Path.cwd()] 

493 except OSError: 

494 return [] 

495 

496 # A dict of output history, keyed with ints from the shell's 

497 # execution count. 

498 output_hist = Dict() 

499 # The text/plain repr of outputs. 

500 output_hist_reprs = Dict() 

501 

502 # The number of the current session in the history database 

503 session_number = Integer() 

504 

505 db_log_output = Bool(False, 

506 help="Should the history database include output? (default: no)" 

507 ).tag(config=True) 

508 db_cache_size = Integer(0, 

509 help="Write to database every x commands (higher values save disk access & power).\n" 

510 "Values of 1 or less effectively disable caching." 

511 ).tag(config=True) 

512 # The input and output caches 

513 db_input_cache = List() 

514 db_output_cache = List() 

515 

516 # History saving in separate thread 

517 save_thread = Instance('IPython.core.history.HistorySavingThread', 

518 allow_none=True) 

519 save_flag = Instance(threading.Event, allow_none=True) 

520 

521 # Private interface 

522 # Variables used to store the three last inputs from the user. On each new 

523 # history update, we populate the user's namespace with these, shifted as 

524 # necessary. 

525 _i00 = Unicode(u'') 

526 _i = Unicode(u'') 

527 _ii = Unicode(u'') 

528 _iii = Unicode(u'') 

529 

530 # A regex matching all forms of the exit command, so that we don't store 

531 # them in the history (it's annoying to rewind the first entry and land on 

532 # an exit call). 

533 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") 

534 

535 def __init__(self, shell=None, config=None, **traits): 

536 """Create a new history manager associated with a shell instance. 

537 """ 

538 super(HistoryManager, self).__init__(shell=shell, config=config, 

539 **traits) 

540 self.save_flag = threading.Event() 

541 self.db_input_cache_lock = threading.Lock() 

542 self.db_output_cache_lock = threading.Lock() 

543 

544 try: 

545 self.new_session() 

546 except sqlite3.OperationalError: 

547 self.log.error("Failed to create history session in %s. History will not be saved.", 

548 self.hist_file, exc_info=True) 

549 self.hist_file = ':memory:' 

550 

551 if self.enabled and self.hist_file != ':memory:': 

552 self.save_thread = HistorySavingThread(self) 

553 self.save_thread.start() 

554 

555 def _get_hist_file_name(self, profile=None): 

556 """Get default history file name based on the Shell's profile. 

557 

558 The profile parameter is ignored, but must exist for compatibility with 

559 the parent class.""" 

560 profile_dir = self.shell.profile_dir.location 

561 return Path(profile_dir) / "history.sqlite" 

562 

563 @only_when_enabled 

564 def new_session(self, conn=None): 

565 """Get a new session number.""" 

566 if conn is None: 

567 conn = self.db 

568 

569 with conn: 

570 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, 

571 NULL, "") """, (datetime.datetime.now(),)) 

572 self.session_number = cur.lastrowid 

573 

574 def end_session(self): 

575 """Close the database session, filling in the end time and line count.""" 

576 self.writeout_cache() 

577 with self.db: 

578 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE 

579 session==?""", (datetime.datetime.now(), 

580 len(self.input_hist_parsed)-1, self.session_number)) 

581 self.session_number = 0 

582 

583 def name_session(self, name): 

584 """Give the current session a name in the history database.""" 

585 with self.db: 

586 self.db.execute("UPDATE sessions SET remark=? WHERE session==?", 

587 (name, self.session_number)) 

588 

589 def reset(self, new_session=True): 

590 """Clear the session history, releasing all object references, and 

591 optionally open a new session.""" 

592 self.output_hist.clear() 

593 # The directory history can't be completely empty 

594 self.dir_hist[:] = [Path.cwd()] 

595 

596 if new_session: 

597 if self.session_number: 

598 self.end_session() 

599 self.input_hist_parsed[:] = [""] 

600 self.input_hist_raw[:] = [""] 

601 self.new_session() 

602 

603 # ------------------------------ 

604 # Methods for retrieving history 

605 # ------------------------------ 

606 def get_session_info(self, session=0): 

607 """Get info about a session. 

608 

609 Parameters 

610 ---------- 

611 session : int 

612 Session number to retrieve. The current session is 0, and negative 

613 numbers count back from current session, so -1 is the previous session. 

614 

615 Returns 

616 ------- 

617 session_id : int 

618 Session ID number 

619 start : datetime 

620 Timestamp for the start of the session. 

621 end : datetime 

622 Timestamp for the end of the session, or None if IPython crashed. 

623 num_cmds : int 

624 Number of commands run, or None if IPython crashed. 

625 remark : unicode 

626 A manually set description. 

627 """ 

628 if session <= 0: 

629 session += self.session_number 

630 

631 return super(HistoryManager, self).get_session_info(session=session) 

632 

633 @catch_corrupt_db 

634 def get_tail(self, n=10, raw=True, output=False, include_latest=False): 

635 """Get the last n lines from the history database. 

636 

637 Most recent entry last. 

638 

639 Completion will be reordered so that that the last ones are when 

640 possible from current session. 

641 

642 Parameters 

643 ---------- 

644 n : int 

645 The number of lines to get 

646 raw, output : bool 

647 See :meth:`get_range` 

648 include_latest : bool 

649 If False (default), n+1 lines are fetched, and the latest one 

650 is discarded. This is intended to be used where the function 

651 is called by a user command, which it should not return. 

652 

653 Returns 

654 ------- 

655 Tuples as :meth:`get_range` 

656 """ 

657 self.writeout_cache() 

658 if not include_latest: 

659 n += 1 

660 # cursor/line/entry 

661 this_cur = list( 

662 self._run_sql( 

663 "WHERE session == ? ORDER BY line DESC LIMIT ? ", 

664 (self.session_number, n), 

665 raw=raw, 

666 output=output, 

667 ) 

668 ) 

669 other_cur = list( 

670 self._run_sql( 

671 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?", 

672 (self.session_number, n), 

673 raw=raw, 

674 output=output, 

675 ) 

676 ) 

677 

678 everything = this_cur + other_cur 

679 

680 everything = everything[:n] 

681 

682 if not include_latest: 

683 return list(everything)[:0:-1] 

684 return list(everything)[::-1] 

685 

686 def _get_range_session(self, start=1, stop=None, raw=True, output=False): 

687 """Get input and output history from the current session. Called by 

688 get_range, and takes similar parameters.""" 

689 input_hist = self.input_hist_raw if raw else self.input_hist_parsed 

690 

691 n = len(input_hist) 

692 if start < 0: 

693 start += n 

694 if not stop or (stop > n): 

695 stop = n 

696 elif stop < 0: 

697 stop += n 

698 

699 for i in range(start, stop): 

700 if output: 

701 line = (input_hist[i], self.output_hist_reprs.get(i)) 

702 else: 

703 line = input_hist[i] 

704 yield (0, i, line) 

705 

706 def get_range(self, session=0, start=1, stop=None, raw=True,output=False): 

707 """Retrieve input by session. 

708 

709 Parameters 

710 ---------- 

711 session : int 

712 Session number to retrieve. The current session is 0, and negative 

713 numbers count back from current session, so -1 is previous session. 

714 start : int 

715 First line to retrieve. 

716 stop : int 

717 End of line range (excluded from output itself). If None, retrieve 

718 to the end of the session. 

719 raw : bool 

720 If True, return untranslated input 

721 output : bool 

722 If True, attempt to include output. This will be 'real' Python 

723 objects for the current session, or text reprs from previous 

724 sessions if db_log_output was enabled at the time. Where no output 

725 is found, None is used. 

726 

727 Returns 

728 ------- 

729 entries 

730 An iterator over the desired lines. Each line is a 3-tuple, either 

731 (session, line, input) if output is False, or 

732 (session, line, (input, output)) if output is True. 

733 """ 

734 if session <= 0: 

735 session += self.session_number 

736 if session==self.session_number: # Current session 

737 return self._get_range_session(start, stop, raw, output) 

738 return super(HistoryManager, self).get_range(session, start, stop, raw, 

739 output) 

740 

741 ## ---------------------------- 

742 ## Methods for storing history: 

743 ## ---------------------------- 

744 def store_inputs(self, line_num, source, source_raw=None): 

745 """Store source and raw input in history and create input cache 

746 variables ``_i*``. 

747 

748 Parameters 

749 ---------- 

750 line_num : int 

751 The prompt number of this input. 

752 source : str 

753 Python input. 

754 source_raw : str, optional 

755 If given, this is the raw input without any IPython transformations 

756 applied to it. If not given, ``source`` is used. 

757 """ 

758 if source_raw is None: 

759 source_raw = source 

760 source = source.rstrip('\n') 

761 source_raw = source_raw.rstrip('\n') 

762 

763 # do not store exit/quit commands 

764 if self._exit_re.match(source_raw.strip()): 

765 return 

766 

767 self.input_hist_parsed.append(source) 

768 self.input_hist_raw.append(source_raw) 

769 

770 with self.db_input_cache_lock: 

771 self.db_input_cache.append((line_num, source, source_raw)) 

772 # Trigger to flush cache and write to DB. 

773 if len(self.db_input_cache) >= self.db_cache_size: 

774 self.save_flag.set() 

775 

776 # update the auto _i variables 

777 self._iii = self._ii 

778 self._ii = self._i 

779 self._i = self._i00 

780 self._i00 = source_raw 

781 

782 # hackish access to user namespace to create _i1,_i2... dynamically 

783 new_i = '_i%s' % line_num 

784 to_main = {'_i': self._i, 

785 '_ii': self._ii, 

786 '_iii': self._iii, 

787 new_i : self._i00 } 

788 

789 if self.shell is not None: 

790 self.shell.push(to_main, interactive=False) 

791 

792 def store_output(self, line_num): 

793 """If database output logging is enabled, this saves all the 

794 outputs from the indicated prompt number to the database. It's 

795 called by run_cell after code has been executed. 

796 

797 Parameters 

798 ---------- 

799 line_num : int 

800 The line number from which to save outputs 

801 """ 

802 if (not self.db_log_output) or (line_num not in self.output_hist_reprs): 

803 return 

804 output = self.output_hist_reprs[line_num] 

805 

806 with self.db_output_cache_lock: 

807 self.db_output_cache.append((line_num, output)) 

808 if self.db_cache_size <= 1: 

809 self.save_flag.set() 

810 

811 def _writeout_input_cache(self, conn): 

812 with conn: 

813 for line in self.db_input_cache: 

814 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", 

815 (self.session_number,)+line) 

816 

817 def _writeout_output_cache(self, conn): 

818 with conn: 

819 for line in self.db_output_cache: 

820 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", 

821 (self.session_number,)+line) 

822 

823 @only_when_enabled 

824 def writeout_cache(self, conn=None): 

825 """Write any entries in the cache to the database.""" 

826 if conn is None: 

827 conn = self.db 

828 

829 with self.db_input_cache_lock: 

830 try: 

831 self._writeout_input_cache(conn) 

832 except sqlite3.IntegrityError: 

833 self.new_session(conn) 

834 print("ERROR! Session/line number was not unique in", 

835 "database. History logging moved to new session", 

836 self.session_number) 

837 try: 

838 # Try writing to the new session. If this fails, don't 

839 # recurse 

840 self._writeout_input_cache(conn) 

841 except sqlite3.IntegrityError: 

842 pass 

843 finally: 

844 self.db_input_cache = [] 

845 

846 with self.db_output_cache_lock: 

847 try: 

848 self._writeout_output_cache(conn) 

849 except sqlite3.IntegrityError: 

850 print("!! Session/line number for output was not unique", 

851 "in database. Output will not be stored.") 

852 finally: 

853 self.db_output_cache = [] 

854 

855 

856class HistorySavingThread(threading.Thread): 

857 """This thread takes care of writing history to the database, so that 

858 the UI isn't held up while that happens. 

859 

860 It waits for the HistoryManager's save_flag to be set, then writes out 

861 the history cache. The main thread is responsible for setting the flag when 

862 the cache size reaches a defined threshold.""" 

863 daemon = True 

864 stop_now = False 

865 enabled = True 

866 def __init__(self, history_manager): 

867 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread") 

868 self.history_manager = history_manager 

869 self.enabled = history_manager.enabled 

870 atexit.register(self.stop) 

871 

872 @only_when_enabled 

873 def run(self): 

874 # We need a separate db connection per thread: 

875 try: 

876 self.db = sqlite3.connect( 

877 str(self.history_manager.hist_file), 

878 **self.history_manager.connection_options, 

879 ) 

880 while True: 

881 self.history_manager.save_flag.wait() 

882 if self.stop_now: 

883 self.db.close() 

884 return 

885 self.history_manager.save_flag.clear() 

886 self.history_manager.writeout_cache(self.db) 

887 except Exception as e: 

888 print(("The history saving thread hit an unexpected error (%s)." 

889 "History will not be written to the database.") % repr(e)) 

890 

891 def stop(self): 

892 """This can be called from the main thread to safely stop this thread. 

893 

894 Note that it does not attempt to write out remaining history before 

895 exiting. That should be done by calling the HistoryManager's 

896 end_session method.""" 

897 self.stop_now = True 

898 self.history_manager.save_flag.set() 

899 self.join() 

900 

901 

902# To match, e.g. ~5/8-~2/3 

903range_re = re.compile(r""" 

904((?P<startsess>~?\d+)/)? 

905(?P<start>\d+)? 

906((?P<sep>[\-:]) 

907 ((?P<endsess>~?\d+)/)? 

908 (?P<end>\d+))? 

909$""", re.VERBOSE) 

910 

911 

912def extract_hist_ranges(ranges_str): 

913 """Turn a string of history ranges into 3-tuples of (session, start, stop). 

914 

915 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current 

916 session". 

917 

918 Examples 

919 -------- 

920 >>> list(extract_hist_ranges("~8/5-~7/4 2")) 

921 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)] 

922 """ 

923 if ranges_str == "": 

924 yield (0, 1, None) # Everything from current session 

925 return 

926 

927 for range_str in ranges_str.split(): 

928 rmatch = range_re.match(range_str) 

929 if not rmatch: 

930 continue 

931 start = rmatch.group("start") 

932 if start: 

933 start = int(start) 

934 end = rmatch.group("end") 

935 # If no end specified, get (a, a + 1) 

936 end = int(end) if end else start + 1 

937 else: # start not specified 

938 if not rmatch.group('startsess'): # no startsess 

939 continue 

940 start = 1 

941 end = None # provide the entire session hist 

942 

943 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3] 

944 end += 1 

945 startsess = rmatch.group("startsess") or "0" 

946 endsess = rmatch.group("endsess") or startsess 

947 startsess = int(startsess.replace("~","-")) 

948 endsess = int(endsess.replace("~","-")) 

949 assert endsess >= startsess, "start session must be earlier than end session" 

950 

951 if endsess == startsess: 

952 yield (startsess, start, end) 

953 continue 

954 # Multiple sessions in one range: 

955 yield (startsess, start, None) 

956 for sess in range(startsess+1, endsess): 

957 yield (sess, 1, None) 

958 yield (endsess, 1, end) 

959 

960 

961def _format_lineno(session, line): 

962 """Helper function to format line numbers properly.""" 

963 if session == 0: 

964 return str(line) 

965 return "%s#%s" % (session, line)