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

391 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:05 +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 @default("connection_options") 

181 def _default_connection_options(self): 

182 return dict(check_same_thread=False) 

183 

184 # The SQLite database 

185 db = Any() 

186 @observe('db') 

187 def _db_changed(self, change): 

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

189 new = change['new'] 

190 connection_types = (DummyDB, sqlite3.Connection) 

191 if not isinstance(new, connection_types): 

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

193 (self.__class__.__name__, new) 

194 raise TraitError(msg) 

195 

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

197 """Create a new history accessor. 

198 

199 Parameters 

200 ---------- 

201 profile : str 

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

203 hist_file : str 

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

205 hist_file overrides profile. 

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

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

208 """ 

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

210 # defer setting hist_file from kwarg until after init, 

211 # otherwise the default kwarg value would clobber any value 

212 # set by config 

213 if hist_file: 

214 self.hist_file = hist_file 

215 

216 try: 

217 self.hist_file 

218 except TraitError: 

219 # No one has set the hist_file, yet. 

220 self.hist_file = self._get_hist_file_name(profile) 

221 

222 self.init_db() 

223 

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

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

226 

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

228 active profile. 

229 

230 Parameters 

231 ---------- 

232 profile : str 

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

234 """ 

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

236 

237 @catch_corrupt_db 

238 def init_db(self): 

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

240 if not self.enabled: 

241 self.db = DummyDB() 

242 return 

243 

244 # use detect_types so that timestamps return datetime objects 

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

246 kwargs.update(self.connection_options) 

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

248 with self.db: 

249 self.db.execute( 

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

251 primary key autoincrement, start timestamp, 

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

253 ) 

254 self.db.execute( 

255 """CREATE TABLE IF NOT EXISTS history 

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

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

258 ) 

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

260 # enabled later. 

261 self.db.execute( 

262 """CREATE TABLE IF NOT EXISTS output_history 

263 (session integer, line integer, output text, 

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

265 ) 

266 # success! reset corrupt db count 

267 self._corrupt_db_counter = 0 

268 

269 def writeout_cache(self): 

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

271 database lookups.""" 

272 pass 

273 

274 ## ------------------------------- 

275 ## Methods for retrieving history: 

276 ## ------------------------------- 

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

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

279 

280 Parameters 

281 ---------- 

282 sql : str 

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

284 params : tuple 

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

286 raw, output : bool 

287 See :meth:`get_range` 

288 latest : bool 

289 Select rows with max (session, line) 

290 

291 Returns 

292 ------- 

293 Tuples as :meth:`get_range` 

294 """ 

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

296 sqlfrom = "history" 

297 if output: 

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

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

300 if latest: 

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

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

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

304 if latest: 

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

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

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

308 return cur 

309 

310 @only_when_enabled 

311 @catch_corrupt_db 

312 def get_session_info(self, session): 

313 """Get info about a session. 

314 

315 Parameters 

316 ---------- 

317 session : int 

318 Session number to retrieve. 

319 

320 Returns 

321 ------- 

322 session_id : int 

323 Session ID number 

324 start : datetime 

325 Timestamp for the start of the session. 

326 end : datetime 

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

328 num_cmds : int 

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

330 remark : unicode 

331 A manually set description. 

332 """ 

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

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

335 

336 @catch_corrupt_db 

337 def get_last_session_id(self): 

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

339 

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

341 :attr:`HistoryManager.session_number`. 

342 """ 

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

344 return record[0] 

345 

346 @catch_corrupt_db 

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

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

349 

350 Parameters 

351 ---------- 

352 n : int 

353 The number of lines to get 

354 raw, output : bool 

355 See :meth:`get_range` 

356 include_latest : bool 

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

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

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

360 

361 Returns 

362 ------- 

363 Tuples as :meth:`get_range` 

364 """ 

365 self.writeout_cache() 

366 if not include_latest: 

367 n += 1 

368 cur = self._run_sql( 

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

370 ) 

371 if not include_latest: 

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

373 return reversed(list(cur)) 

374 

375 @catch_corrupt_db 

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

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

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

379 * and ?). 

380 

381 Parameters 

382 ---------- 

383 pattern : str 

384 The wildcarded pattern to match when searching 

385 search_raw : bool 

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

387 raw, output : bool 

388 See :meth:`get_range` 

389 n : None or int 

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

391 returned entries. 

392 unique : bool 

393 When it is true, return only unique entries. 

394 

395 Returns 

396 ------- 

397 Tuples as :meth:`get_range` 

398 """ 

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

400 if output: 

401 tosearch = "history." + tosearch 

402 self.writeout_cache() 

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

404 params = (pattern,) 

405 if unique: 

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

407 if n is not None: 

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

409 params += (n,) 

410 elif unique: 

411 sqlform += " ORDER BY session, line" 

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

413 if n is not None: 

414 return reversed(list(cur)) 

415 return cur 

416 

417 @catch_corrupt_db 

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

419 """Retrieve input by session. 

420 

421 Parameters 

422 ---------- 

423 session : int 

424 Session number to retrieve. 

425 start : int 

426 First line to retrieve. 

427 stop : int 

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

429 to the end of the session. 

430 raw : bool 

431 If True, return untranslated input 

432 output : bool 

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

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

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

436 is found, None is used. 

437 

438 Returns 

439 ------- 

440 entries 

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

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

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

444 """ 

445 if stop: 

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

447 params = (session, start, stop) 

448 else: 

449 lineclause = "line>=?" 

450 params = (session, start) 

451 

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

453 params, raw=raw, output=output) 

454 

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

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

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

458 

459 Parameters 

460 ---------- 

461 rangestr : str 

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

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

464 

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

466 

467 raw, output : bool 

468 As :meth:`get_range` 

469 

470 Returns 

471 ------- 

472 Tuples as :meth:`get_range` 

473 """ 

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

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

476 yield line 

477 

478 

479class HistoryManager(HistoryAccessor): 

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

481 """ 

482 # Public interface 

483 

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

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

486 allow_none=True) 

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

488 # so that we can index them starting from 1 

489 input_hist_parsed = List([""]) 

490 input_hist_raw = List([""]) 

491 # A list of directories visited during session 

492 dir_hist = List() 

493 @default('dir_hist') 

494 def _dir_hist_default(self): 

495 try: 

496 return [Path.cwd()] 

497 except OSError: 

498 return [] 

499 

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

501 # execution count. 

502 output_hist = Dict() 

503 # The text/plain repr of outputs. 

504 output_hist_reprs = Dict() 

505 

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

507 session_number = Integer() 

508 

509 db_log_output = Bool(False, 

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

511 ).tag(config=True) 

512 db_cache_size = Integer(0, 

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

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

515 ).tag(config=True) 

516 # The input and output caches 

517 db_input_cache = List() 

518 db_output_cache = List() 

519 

520 # History saving in separate thread 

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

522 allow_none=True) 

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

524 

525 # Private interface 

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

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

528 # necessary. 

529 _i00 = Unicode(u'') 

530 _i = Unicode(u'') 

531 _ii = Unicode(u'') 

532 _iii = Unicode(u'') 

533 

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

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

536 # an exit call). 

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

538 

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

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

541 """ 

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

543 **traits) 

544 self.save_flag = threading.Event() 

545 self.db_input_cache_lock = threading.Lock() 

546 self.db_output_cache_lock = threading.Lock() 

547 

548 try: 

549 self.new_session() 

550 except sqlite3.OperationalError: 

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

552 self.hist_file, exc_info=True) 

553 self.hist_file = ':memory:' 

554 

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

556 self.save_thread = HistorySavingThread(self) 

557 self.save_thread.start() 

558 

559 def _get_hist_file_name(self, profile=None): 

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

561 

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

563 the parent class.""" 

564 profile_dir = self.shell.profile_dir.location 

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

566 

567 @only_when_enabled 

568 def new_session(self, conn=None): 

569 """Get a new session number.""" 

570 if conn is None: 

571 conn = self.db 

572 

573 with conn: 

574 cur = conn.execute( 

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

576 NULL, '') """, 

577 (datetime.datetime.now().isoformat(" "),), 

578 ) 

579 self.session_number = cur.lastrowid 

580 

581 def end_session(self): 

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

583 self.writeout_cache() 

584 with self.db: 

585 self.db.execute( 

586 """UPDATE sessions SET end=?, num_cmds=? WHERE 

587 session==?""", 

588 ( 

589 datetime.datetime.now().isoformat(" "), 

590 len(self.input_hist_parsed) - 1, 

591 self.session_number, 

592 ), 

593 ) 

594 self.session_number = 0 

595 

596 def name_session(self, name): 

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

598 with self.db: 

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

600 (name, self.session_number)) 

601 

602 def reset(self, new_session=True): 

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

604 optionally open a new session.""" 

605 self.output_hist.clear() 

606 # The directory history can't be completely empty 

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

608 

609 if new_session: 

610 if self.session_number: 

611 self.end_session() 

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

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

614 self.new_session() 

615 

616 # ------------------------------ 

617 # Methods for retrieving history 

618 # ------------------------------ 

619 def get_session_info(self, session=0): 

620 """Get info about a session. 

621 

622 Parameters 

623 ---------- 

624 session : int 

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

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

627 

628 Returns 

629 ------- 

630 session_id : int 

631 Session ID number 

632 start : datetime 

633 Timestamp for the start of the session. 

634 end : datetime 

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

636 num_cmds : int 

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

638 remark : unicode 

639 A manually set description. 

640 """ 

641 if session <= 0: 

642 session += self.session_number 

643 

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

645 

646 @catch_corrupt_db 

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

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

649 

650 Most recent entry last. 

651 

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

653 possible from current session. 

654 

655 Parameters 

656 ---------- 

657 n : int 

658 The number of lines to get 

659 raw, output : bool 

660 See :meth:`get_range` 

661 include_latest : bool 

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

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

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

665 

666 Returns 

667 ------- 

668 Tuples as :meth:`get_range` 

669 """ 

670 self.writeout_cache() 

671 if not include_latest: 

672 n += 1 

673 # cursor/line/entry 

674 this_cur = list( 

675 self._run_sql( 

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

677 (self.session_number, n), 

678 raw=raw, 

679 output=output, 

680 ) 

681 ) 

682 other_cur = list( 

683 self._run_sql( 

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

685 (self.session_number, n), 

686 raw=raw, 

687 output=output, 

688 ) 

689 ) 

690 

691 everything = this_cur + other_cur 

692 

693 everything = everything[:n] 

694 

695 if not include_latest: 

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

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

698 

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

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

701 get_range, and takes similar parameters.""" 

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

703 

704 n = len(input_hist) 

705 if start < 0: 

706 start += n 

707 if not stop or (stop > n): 

708 stop = n 

709 elif stop < 0: 

710 stop += n 

711 

712 for i in range(start, stop): 

713 if output: 

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

715 else: 

716 line = input_hist[i] 

717 yield (0, i, line) 

718 

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

720 """Retrieve input by session. 

721 

722 Parameters 

723 ---------- 

724 session : int 

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

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

727 start : int 

728 First line to retrieve. 

729 stop : int 

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

731 to the end of the session. 

732 raw : bool 

733 If True, return untranslated input 

734 output : bool 

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

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

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

738 is found, None is used. 

739 

740 Returns 

741 ------- 

742 entries 

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

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

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

746 """ 

747 if session <= 0: 

748 session += self.session_number 

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

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

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

752 output) 

753 

754 ## ---------------------------- 

755 ## Methods for storing history: 

756 ## ---------------------------- 

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

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

759 variables ``_i*``. 

760 

761 Parameters 

762 ---------- 

763 line_num : int 

764 The prompt number of this input. 

765 source : str 

766 Python input. 

767 source_raw : str, optional 

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

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

770 """ 

771 if source_raw is None: 

772 source_raw = source 

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

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

775 

776 # do not store exit/quit commands 

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

778 return 

779 

780 self.input_hist_parsed.append(source) 

781 self.input_hist_raw.append(source_raw) 

782 

783 with self.db_input_cache_lock: 

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

785 # Trigger to flush cache and write to DB. 

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

787 self.save_flag.set() 

788 

789 # update the auto _i variables 

790 self._iii = self._ii 

791 self._ii = self._i 

792 self._i = self._i00 

793 self._i00 = source_raw 

794 

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

796 new_i = '_i%s' % line_num 

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

798 '_ii': self._ii, 

799 '_iii': self._iii, 

800 new_i : self._i00 } 

801 

802 if self.shell is not None: 

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

804 

805 def store_output(self, line_num): 

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

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

808 called by run_cell after code has been executed. 

809 

810 Parameters 

811 ---------- 

812 line_num : int 

813 The line number from which to save outputs 

814 """ 

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

816 return 

817 output = self.output_hist_reprs[line_num] 

818 

819 with self.db_output_cache_lock: 

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

821 if self.db_cache_size <= 1: 

822 self.save_flag.set() 

823 

824 def _writeout_input_cache(self, conn): 

825 with conn: 

826 for line in self.db_input_cache: 

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

828 (self.session_number,)+line) 

829 

830 def _writeout_output_cache(self, conn): 

831 with conn: 

832 for line in self.db_output_cache: 

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

834 (self.session_number,)+line) 

835 

836 @only_when_enabled 

837 def writeout_cache(self, conn=None): 

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

839 if conn is None: 

840 conn = self.db 

841 

842 with self.db_input_cache_lock: 

843 try: 

844 self._writeout_input_cache(conn) 

845 except sqlite3.IntegrityError: 

846 self.new_session(conn) 

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

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

849 self.session_number) 

850 try: 

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

852 # recurse 

853 self._writeout_input_cache(conn) 

854 except sqlite3.IntegrityError: 

855 pass 

856 finally: 

857 self.db_input_cache = [] 

858 

859 with self.db_output_cache_lock: 

860 try: 

861 self._writeout_output_cache(conn) 

862 except sqlite3.IntegrityError: 

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

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

865 finally: 

866 self.db_output_cache = [] 

867 

868 

869class HistorySavingThread(threading.Thread): 

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

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

872 

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

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

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

876 daemon = True 

877 stop_now = False 

878 enabled = True 

879 def __init__(self, history_manager): 

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

881 self.history_manager = history_manager 

882 self.enabled = history_manager.enabled 

883 atexit.register(self.stop) 

884 

885 @only_when_enabled 

886 def run(self): 

887 # We need a separate db connection per thread: 

888 try: 

889 self.db = sqlite3.connect( 

890 str(self.history_manager.hist_file), 

891 **self.history_manager.connection_options, 

892 ) 

893 while True: 

894 self.history_manager.save_flag.wait() 

895 if self.stop_now: 

896 self.db.close() 

897 return 

898 self.history_manager.save_flag.clear() 

899 self.history_manager.writeout_cache(self.db) 

900 except Exception as e: 

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

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

903 

904 def stop(self): 

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

906 

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

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

909 end_session method.""" 

910 self.stop_now = True 

911 self.history_manager.save_flag.set() 

912 self.join() 

913 

914 

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

916range_re = re.compile(r""" 

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

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

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

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

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

922$""", re.VERBOSE) 

923 

924 

925def extract_hist_ranges(ranges_str): 

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

927 

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

929 session". 

930 

931 Examples 

932 -------- 

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

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

935 """ 

936 if ranges_str == "": 

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

938 return 

939 

940 for range_str in ranges_str.split(): 

941 rmatch = range_re.match(range_str) 

942 if not rmatch: 

943 continue 

944 start = rmatch.group("start") 

945 if start: 

946 start = int(start) 

947 end = rmatch.group("end") 

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

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

950 else: # start not specified 

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

952 continue 

953 start = 1 

954 end = None # provide the entire session hist 

955 

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

957 end += 1 

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

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

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

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

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

963 

964 if endsess == startsess: 

965 yield (startsess, start, end) 

966 continue 

967 # Multiple sessions in one range: 

968 yield (startsess, start, None) 

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

970 yield (sess, 1, None) 

971 yield (endsess, 1, end) 

972 

973 

974def _format_lineno(session, line): 

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

976 if session == 0: 

977 return str(line) 

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