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.4.4, created at 2024-04-20 06:09 +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( 

571 """INSERT INTO sessions VALUES (NULL, ?, NULL, 

572 NULL, '') """, 

573 (datetime.datetime.now(),), 

574 ) 

575 self.session_number = cur.lastrowid 

576 

577 def end_session(self): 

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

579 self.writeout_cache() 

580 with self.db: 

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

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

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

584 self.session_number = 0 

585 

586 def name_session(self, name): 

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

588 with self.db: 

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

590 (name, self.session_number)) 

591 

592 def reset(self, new_session=True): 

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

594 optionally open a new session.""" 

595 self.output_hist.clear() 

596 # The directory history can't be completely empty 

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

598 

599 if new_session: 

600 if self.session_number: 

601 self.end_session() 

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

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

604 self.new_session() 

605 

606 # ------------------------------ 

607 # Methods for retrieving history 

608 # ------------------------------ 

609 def get_session_info(self, session=0): 

610 """Get info about a session. 

611 

612 Parameters 

613 ---------- 

614 session : int 

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

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

617 

618 Returns 

619 ------- 

620 session_id : int 

621 Session ID number 

622 start : datetime 

623 Timestamp for the start of the session. 

624 end : datetime 

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

626 num_cmds : int 

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

628 remark : unicode 

629 A manually set description. 

630 """ 

631 if session <= 0: 

632 session += self.session_number 

633 

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

635 

636 @catch_corrupt_db 

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

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

639 

640 Most recent entry last. 

641 

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

643 possible from current session. 

644 

645 Parameters 

646 ---------- 

647 n : int 

648 The number of lines to get 

649 raw, output : bool 

650 See :meth:`get_range` 

651 include_latest : bool 

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

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

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

655 

656 Returns 

657 ------- 

658 Tuples as :meth:`get_range` 

659 """ 

660 self.writeout_cache() 

661 if not include_latest: 

662 n += 1 

663 # cursor/line/entry 

664 this_cur = list( 

665 self._run_sql( 

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

667 (self.session_number, n), 

668 raw=raw, 

669 output=output, 

670 ) 

671 ) 

672 other_cur = list( 

673 self._run_sql( 

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

675 (self.session_number, n), 

676 raw=raw, 

677 output=output, 

678 ) 

679 ) 

680 

681 everything = this_cur + other_cur 

682 

683 everything = everything[:n] 

684 

685 if not include_latest: 

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

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

688 

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

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

691 get_range, and takes similar parameters.""" 

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

693 

694 n = len(input_hist) 

695 if start < 0: 

696 start += n 

697 if not stop or (stop > n): 

698 stop = n 

699 elif stop < 0: 

700 stop += n 

701 

702 for i in range(start, stop): 

703 if output: 

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

705 else: 

706 line = input_hist[i] 

707 yield (0, i, line) 

708 

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

710 """Retrieve input by session. 

711 

712 Parameters 

713 ---------- 

714 session : int 

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

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

717 start : int 

718 First line to retrieve. 

719 stop : int 

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

721 to the end of the session. 

722 raw : bool 

723 If True, return untranslated input 

724 output : bool 

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

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

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

728 is found, None is used. 

729 

730 Returns 

731 ------- 

732 entries 

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

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

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

736 """ 

737 if session <= 0: 

738 session += self.session_number 

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

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

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

742 output) 

743 

744 ## ---------------------------- 

745 ## Methods for storing history: 

746 ## ---------------------------- 

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

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

749 variables ``_i*``. 

750 

751 Parameters 

752 ---------- 

753 line_num : int 

754 The prompt number of this input. 

755 source : str 

756 Python input. 

757 source_raw : str, optional 

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

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

760 """ 

761 if source_raw is None: 

762 source_raw = source 

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

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

765 

766 # do not store exit/quit commands 

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

768 return 

769 

770 self.input_hist_parsed.append(source) 

771 self.input_hist_raw.append(source_raw) 

772 

773 with self.db_input_cache_lock: 

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

775 # Trigger to flush cache and write to DB. 

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

777 self.save_flag.set() 

778 

779 # update the auto _i variables 

780 self._iii = self._ii 

781 self._ii = self._i 

782 self._i = self._i00 

783 self._i00 = source_raw 

784 

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

786 new_i = '_i%s' % line_num 

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

788 '_ii': self._ii, 

789 '_iii': self._iii, 

790 new_i : self._i00 } 

791 

792 if self.shell is not None: 

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

794 

795 def store_output(self, line_num): 

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

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

798 called by run_cell after code has been executed. 

799 

800 Parameters 

801 ---------- 

802 line_num : int 

803 The line number from which to save outputs 

804 """ 

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

806 return 

807 output = self.output_hist_reprs[line_num] 

808 

809 with self.db_output_cache_lock: 

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

811 if self.db_cache_size <= 1: 

812 self.save_flag.set() 

813 

814 def _writeout_input_cache(self, conn): 

815 with conn: 

816 for line in self.db_input_cache: 

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

818 (self.session_number,)+line) 

819 

820 def _writeout_output_cache(self, conn): 

821 with conn: 

822 for line in self.db_output_cache: 

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

824 (self.session_number,)+line) 

825 

826 @only_when_enabled 

827 def writeout_cache(self, conn=None): 

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

829 if conn is None: 

830 conn = self.db 

831 

832 with self.db_input_cache_lock: 

833 try: 

834 self._writeout_input_cache(conn) 

835 except sqlite3.IntegrityError: 

836 self.new_session(conn) 

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

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

839 self.session_number) 

840 try: 

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

842 # recurse 

843 self._writeout_input_cache(conn) 

844 except sqlite3.IntegrityError: 

845 pass 

846 finally: 

847 self.db_input_cache = [] 

848 

849 with self.db_output_cache_lock: 

850 try: 

851 self._writeout_output_cache(conn) 

852 except sqlite3.IntegrityError: 

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

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

855 finally: 

856 self.db_output_cache = [] 

857 

858 

859class HistorySavingThread(threading.Thread): 

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

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

862 

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

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

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

866 daemon = True 

867 stop_now = False 

868 enabled = True 

869 def __init__(self, history_manager): 

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

871 self.history_manager = history_manager 

872 self.enabled = history_manager.enabled 

873 atexit.register(self.stop) 

874 

875 @only_when_enabled 

876 def run(self): 

877 # We need a separate db connection per thread: 

878 try: 

879 self.db = sqlite3.connect( 

880 str(self.history_manager.hist_file), 

881 **self.history_manager.connection_options, 

882 ) 

883 while True: 

884 self.history_manager.save_flag.wait() 

885 if self.stop_now: 

886 self.db.close() 

887 return 

888 self.history_manager.save_flag.clear() 

889 self.history_manager.writeout_cache(self.db) 

890 except Exception as e: 

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

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

893 

894 def stop(self): 

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

896 

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

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

899 end_session method.""" 

900 self.stop_now = True 

901 self.history_manager.save_flag.set() 

902 self.join() 

903 

904 

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

906range_re = re.compile(r""" 

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

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

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

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

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

912$""", re.VERBOSE) 

913 

914 

915def extract_hist_ranges(ranges_str): 

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

917 

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

919 session". 

920 

921 Examples 

922 -------- 

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

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

925 """ 

926 if ranges_str == "": 

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

928 return 

929 

930 for range_str in ranges_str.split(): 

931 rmatch = range_re.match(range_str) 

932 if not rmatch: 

933 continue 

934 start = rmatch.group("start") 

935 if start: 

936 start = int(start) 

937 end = rmatch.group("end") 

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

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

940 else: # start not specified 

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

942 continue 

943 start = 1 

944 end = None # provide the entire session hist 

945 

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

947 end += 1 

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

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

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

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

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

953 

954 if endsess == startsess: 

955 yield (startsess, start, end) 

956 continue 

957 # Multiple sessions in one range: 

958 yield (startsess, start, None) 

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

960 yield (sess, 1, None) 

961 yield (endsess, 1, end) 

962 

963 

964def _format_lineno(session, line): 

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

966 if session == 0: 

967 return str(line) 

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