##// END OF EJS Templates
Fix HistoryAccessor.get_tail bug (#13666)...
Aleksey Bogdanov -
Show More
@@ -202,7 +202,6 b' class HistoryAccessor(HistoryAccessorBase):'
202 202 config : :class:`~traitlets.config.loader.Config`
203 203 Config object. hist_file can also be set through this.
204 204 """
205 # We need a pointer back to the shell for various tasks.
206 205 super(HistoryAccessor, self).__init__(**traits)
207 206 # defer setting hist_file from kwarg until after init,
208 207 # otherwise the default kwarg value would clobber any value
@@ -344,11 +343,6 b' class HistoryAccessor(HistoryAccessorBase):'
344 343 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
345 344 """Get the last n lines from the history database.
346 345
347 Most recent entry last.
348
349 Completion will be reordered so that that the last ones are when
350 possible from current session.
351
352 346 Parameters
353 347 ----------
354 348 n : int
@@ -367,31 +361,11 b' class HistoryAccessor(HistoryAccessorBase):'
367 361 self.writeout_cache()
368 362 if not include_latest:
369 363 n += 1
370 # cursor/line/entry
371 this_cur = list(
372 self._run_sql(
373 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
374 (self.session_number, n),
375 raw=raw,
376 output=output,
377 )
378 )
379 other_cur = list(
380 self._run_sql(
381 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
382 (self.session_number, n),
383 raw=raw,
384 output=output,
385 )
386 )
387
388 everything = this_cur + other_cur
389
390 everything = everything[:n]
391
364 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
365 (n,), raw=raw, output=output)
392 366 if not include_latest:
393 return list(everything)[:0:-1]
394 return list(everything)[::-1]
367 return reversed(list(cur)[1:])
368 return reversed(list(cur))
395 369
396 370 @catch_corrupt_db
397 371 def search(self, pattern="*", raw=True, search_raw=True,
@@ -560,7 +534,6 b' class HistoryManager(HistoryAccessor):'
560 534 def __init__(self, shell=None, config=None, **traits):
561 535 """Create a new history manager associated with a shell instance.
562 536 """
563 # We need a pointer back to the shell for various tasks.
564 537 super(HistoryManager, self).__init__(shell=shell, config=config,
565 538 **traits)
566 539 self.save_flag = threading.Event()
@@ -656,6 +629,59 b' class HistoryManager(HistoryAccessor):'
656 629
657 630 return super(HistoryManager, self).get_session_info(session=session)
658 631
632 @catch_corrupt_db
633 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
634 """Get the last n lines from the history database.
635
636 Most recent entry last.
637
638 Completion will be reordered so that that the last ones are when
639 possible from current session.
640
641 Parameters
642 ----------
643 n : int
644 The number of lines to get
645 raw, output : bool
646 See :meth:`get_range`
647 include_latest : bool
648 If False (default), n+1 lines are fetched, and the latest one
649 is discarded. This is intended to be used where the function
650 is called by a user command, which it should not return.
651
652 Returns
653 -------
654 Tuples as :meth:`get_range`
655 """
656 self.writeout_cache()
657 if not include_latest:
658 n += 1
659 # cursor/line/entry
660 this_cur = list(
661 self._run_sql(
662 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
663 (self.session_number, n),
664 raw=raw,
665 output=output,
666 )
667 )
668 other_cur = list(
669 self._run_sql(
670 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
671 (self.session_number, n),
672 raw=raw,
673 output=output,
674 )
675 )
676
677 everything = this_cur + other_cur
678
679 everything = everything[:n]
680
681 if not include_latest:
682 return list(everything)[:0:-1]
683 return list(everything)[::-1]
684
659 685 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
660 686 """Get input and output history from the current session. Called by
661 687 get_range, and takes similar parameters."""
@@ -17,7 +17,7 b' from tempfile import TemporaryDirectory'
17 17 # our own packages
18 18 from traitlets.config.loader import Config
19 19
20 from IPython.core.history import HistoryManager, extract_hist_ranges
20 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
21 21
22 22
23 23 def test_proper_default_encoding():
@@ -227,3 +227,68 b' def test_histmanager_disabled():'
227 227
228 228 # hist_file should not be created
229 229 assert hist_file.exists() is False
230
231 def test_get_tail_session_awareness():
232 """Test .get_tail() is:
233 - session specific in HistoryManager
234 - session agnostic in HistoryAccessor
235 same for .get_last_session_id()
236 """
237 ip = get_ipython()
238 with TemporaryDirectory() as tmpdir:
239 tmp_path = Path(tmpdir)
240 hist_file = tmp_path / "history.sqlite"
241 get_source = lambda x: x[2]
242
243 # hm1 creates a new session and adds history entries,
244 # ha catches up
245 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
246 hm1_last_sid = hm1.get_last_session_id
247 ha = HistoryAccessor(hist_file=hist_file)
248 ha_last_sid = ha.get_last_session_id
249
250 hist1 = ["a=1", "b=1", "c=1"]
251 for i, h in enumerate(hist1 + [""], start=1):
252 hm1.store_inputs(i, h)
253 assert list(map(get_source, hm1.get_tail())) == hist1
254 assert list(map(get_source, ha.get_tail())) == hist1
255 sid1 = hm1_last_sid()
256 assert sid1 is not None
257 assert ha_last_sid() == sid1
258
259 # hm2 creates a new session and adds entries,
260 # ha catches up
261 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
262 hm2_last_sid = hm2.get_last_session_id
263
264 hist2 = ["a=2", "b=2", "c=2"]
265 for i, h in enumerate(hist2 + [""], start=1):
266 hm2.store_inputs(i, h)
267 tail = hm2.get_tail(n=3)
268 assert list(map(get_source, tail)) == hist2
269 tail = ha.get_tail(n=3)
270 assert list(map(get_source, tail)) == hist2
271 sid2 = hm2_last_sid()
272 assert sid2 is not None
273 assert ha_last_sid() == sid2
274 assert sid2 != sid1
275
276 # but hm1 still maintains its point of reference
277 # and adding more entries to it doesn't change others
278 # immediate perspective
279 assert hm1_last_sid() == sid1
280 tail = hm1.get_tail(n=3)
281 assert list(map(get_source, tail)) == hist1
282
283 hist3 = ["a=3", "b=3", "c=3"]
284 for i, h in enumerate(hist3 + [""], start=5):
285 hm1.store_inputs(i, h)
286 tail = hm1.get_tail(n=7)
287 assert list(map(get_source, tail)) == hist1 + [""] + hist3
288 tail = hm2.get_tail(n=3)
289 assert list(map(get_source, tail)) == hist2
290 tail = ha.get_tail(n=3)
291 assert list(map(get_source, tail)) == hist2
292 assert hm1_last_sid() == sid1
293 assert hm2_last_sid() == sid2
294 assert ha_last_sid() == sid2
General Comments 0
You need to be logged in to leave comments. Login now