Show More
@@ -202,7 +202,6 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
202 | config : :class:`~traitlets.config.loader.Config` |
|
202 | config : :class:`~traitlets.config.loader.Config` | |
203 | Config object. hist_file can also be set through this. |
|
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 | super(HistoryAccessor, self).__init__(**traits) |
|
205 | super(HistoryAccessor, self).__init__(**traits) | |
207 | # defer setting hist_file from kwarg until after init, |
|
206 | # defer setting hist_file from kwarg until after init, | |
208 | # otherwise the default kwarg value would clobber any value |
|
207 | # otherwise the default kwarg value would clobber any value | |
@@ -344,11 +343,6 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
344 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): |
|
343 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): | |
345 | """Get the last n lines from the history database. |
|
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 | Parameters |
|
346 | Parameters | |
353 | ---------- |
|
347 | ---------- | |
354 | n : int |
|
348 | n : int | |
@@ -367,31 +361,12 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
367 | self.writeout_cache() |
|
361 | self.writeout_cache() | |
368 | if not include_latest: |
|
362 | if not include_latest: | |
369 | n += 1 |
|
363 | n += 1 | |
370 | # cursor/line/entry |
|
364 | cur = self._run_sql( | |
371 | this_cur = list( |
|
365 | "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output | |
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 | ) |
|
366 | ) | |
387 |
|
||||
388 | everything = this_cur + other_cur |
|
|||
389 |
|
||||
390 | everything = everything[:n] |
|
|||
391 |
|
||||
392 | if not include_latest: |
|
367 | if not include_latest: | |
393 |
return list( |
|
368 | return reversed(list(cur)[1:]) | |
394 |
return list( |
|
369 | return reversed(list(cur)) | |
395 |
|
370 | |||
396 | @catch_corrupt_db |
|
371 | @catch_corrupt_db | |
397 | def search(self, pattern="*", raw=True, search_raw=True, |
|
372 | def search(self, pattern="*", raw=True, search_raw=True, | |
@@ -560,7 +535,6 b' class HistoryManager(HistoryAccessor):' | |||||
560 | def __init__(self, shell=None, config=None, **traits): |
|
535 | def __init__(self, shell=None, config=None, **traits): | |
561 | """Create a new history manager associated with a shell instance. |
|
536 | """Create a new history manager associated with a shell instance. | |
562 | """ |
|
537 | """ | |
563 | # We need a pointer back to the shell for various tasks. |
|
|||
564 | super(HistoryManager, self).__init__(shell=shell, config=config, |
|
538 | super(HistoryManager, self).__init__(shell=shell, config=config, | |
565 | **traits) |
|
539 | **traits) | |
566 | self.save_flag = threading.Event() |
|
540 | self.save_flag = threading.Event() | |
@@ -656,6 +630,59 b' class HistoryManager(HistoryAccessor):' | |||||
656 |
|
630 | |||
657 | return super(HistoryManager, self).get_session_info(session=session) |
|
631 | return super(HistoryManager, self).get_session_info(session=session) | |
658 |
|
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 | ||||
659 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): |
|
686 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): | |
660 | """Get input and output history from the current session. Called by |
|
687 | """Get input and output history from the current session. Called by | |
661 | get_range, and takes similar parameters.""" |
|
688 | get_range, and takes similar parameters.""" |
@@ -17,7 +17,7 b' from tempfile import TemporaryDirectory' | |||||
17 | # our own packages |
|
17 | # our own packages | |
18 | from traitlets.config.loader import Config |
|
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 | def test_proper_default_encoding(): |
|
23 | def test_proper_default_encoding(): | |
@@ -227,3 +227,81 b' def test_histmanager_disabled():' | |||||
227 |
|
227 | |||
228 | # hist_file should not be created |
|
228 | # hist_file should not be created | |
229 | assert hist_file.exists() is False |
|
229 | assert hist_file.exists() is False | |
|
230 | ||||
|
231 | ||||
|
232 | def test_get_tail_session_awareness(): | |||
|
233 | """Test .get_tail() is: | |||
|
234 | - session specific in HistoryManager | |||
|
235 | - session agnostic in HistoryAccessor | |||
|
236 | same for .get_last_session_id() | |||
|
237 | """ | |||
|
238 | ip = get_ipython() | |||
|
239 | with TemporaryDirectory() as tmpdir: | |||
|
240 | tmp_path = Path(tmpdir) | |||
|
241 | hist_file = tmp_path / "history.sqlite" | |||
|
242 | get_source = lambda x: x[2] | |||
|
243 | hm1 = None | |||
|
244 | hm2 = None | |||
|
245 | ha = None | |||
|
246 | try: | |||
|
247 | # hm1 creates a new session and adds history entries, | |||
|
248 | # ha catches up | |||
|
249 | hm1 = HistoryManager(shell=ip, hist_file=hist_file) | |||
|
250 | hm1_last_sid = hm1.get_last_session_id | |||
|
251 | ha = HistoryAccessor(hist_file=hist_file) | |||
|
252 | ha_last_sid = ha.get_last_session_id | |||
|
253 | ||||
|
254 | hist1 = ["a=1", "b=1", "c=1"] | |||
|
255 | for i, h in enumerate(hist1 + [""], start=1): | |||
|
256 | hm1.store_inputs(i, h) | |||
|
257 | assert list(map(get_source, hm1.get_tail())) == hist1 | |||
|
258 | assert list(map(get_source, ha.get_tail())) == hist1 | |||
|
259 | sid1 = hm1_last_sid() | |||
|
260 | assert sid1 is not None | |||
|
261 | assert ha_last_sid() == sid1 | |||
|
262 | ||||
|
263 | # hm2 creates a new session and adds entries, | |||
|
264 | # ha catches up | |||
|
265 | hm2 = HistoryManager(shell=ip, hist_file=hist_file) | |||
|
266 | hm2_last_sid = hm2.get_last_session_id | |||
|
267 | ||||
|
268 | hist2 = ["a=2", "b=2", "c=2"] | |||
|
269 | for i, h in enumerate(hist2 + [""], start=1): | |||
|
270 | hm2.store_inputs(i, h) | |||
|
271 | tail = hm2.get_tail(n=3) | |||
|
272 | assert list(map(get_source, tail)) == hist2 | |||
|
273 | tail = ha.get_tail(n=3) | |||
|
274 | assert list(map(get_source, tail)) == hist2 | |||
|
275 | sid2 = hm2_last_sid() | |||
|
276 | assert sid2 is not None | |||
|
277 | assert ha_last_sid() == sid2 | |||
|
278 | assert sid2 != sid1 | |||
|
279 | ||||
|
280 | # but hm1 still maintains its point of reference | |||
|
281 | # and adding more entries to it doesn't change others | |||
|
282 | # immediate perspective | |||
|
283 | assert hm1_last_sid() == sid1 | |||
|
284 | tail = hm1.get_tail(n=3) | |||
|
285 | assert list(map(get_source, tail)) == hist1 | |||
|
286 | ||||
|
287 | hist3 = ["a=3", "b=3", "c=3"] | |||
|
288 | for i, h in enumerate(hist3 + [""], start=5): | |||
|
289 | hm1.store_inputs(i, h) | |||
|
290 | tail = hm1.get_tail(n=7) | |||
|
291 | assert list(map(get_source, tail)) == hist1 + [""] + hist3 | |||
|
292 | tail = hm2.get_tail(n=3) | |||
|
293 | assert list(map(get_source, tail)) == hist2 | |||
|
294 | tail = ha.get_tail(n=3) | |||
|
295 | assert list(map(get_source, tail)) == hist2 | |||
|
296 | assert hm1_last_sid() == sid1 | |||
|
297 | assert hm2_last_sid() == sid2 | |||
|
298 | assert ha_last_sid() == sid2 | |||
|
299 | finally: | |||
|
300 | if hm1: | |||
|
301 | hm1.save_thread.stop() | |||
|
302 | hm1.db.close() | |||
|
303 | if hm2: | |||
|
304 | hm2.save_thread.stop() | |||
|
305 | hm2.db.close() | |||
|
306 | if ha: | |||
|
307 | ha.db.close() |
General Comments 0
You need to be logged in to leave comments.
Login now