##// END OF EJS Templates
Add a number of types to history.py...
M Bussonnier -
Show More
@@ -1,5 +1,7
1 1 """ History related magics and functionality """
2 2
3 from __future__ import annotations
4
3 5 # Copyright (c) IPython Development Team.
4 6 # Distributed under the terms of the Modified BSD License.
5 7
@@ -7,7 +9,8
7 9 import atexit
8 10 import datetime
9 11 import re
10 import sqlite3
12
13
11 14 import threading
12 15 from pathlib import Path
13 16
@@ -29,31 +32,55 from traitlets.config.configurable import LoggingConfigurable
29 32
30 33 from IPython.paths import locate_profile
31 34 from IPython.utils.decorators import undoc
35 from typing import Iterable, Tuple, Optional, TYPE_CHECKING
36 import typing
37
38 if TYPE_CHECKING:
39 from IPython.core.interactiveshell import InteractiveShell
40 from IPython.config.Configuration import Configuration
41
42 try:
43 from sqlite3 import DatabaseError, OperationalError
44 import sqlite3
45
46 sqlite3_found = True
47 except ModuleNotFoundError:
48 sqlite3_found = False
49
50 class DatabaseError(Exception): # type: ignore [no-redef]
51 pass
52
53 class OperationalError(Exception): # type: ignore [no-redef]
54 pass
55
56
57 InOrInOut = typing.Union[str, Tuple[str, Optional[str]]]
32 58
33 59 #-----------------------------------------------------------------------------
34 60 # Classes and functions
35 61 #-----------------------------------------------------------------------------
36 62
37 63 @undoc
38 class DummyDB(object):
64 class DummyDB:
39 65 """Dummy DB that will act as a black hole for history.
40 66
41 67 Only used in the absence of sqlite"""
42 def execute(*args, **kwargs):
68
69 def execute(*args: typing.Any, **kwargs: typing.Any) -> typing.List:
43 70 return []
44 71
45 def commit(self, *args, **kwargs):
72 def commit(self, *args, **kwargs): # type: ignore [no-untyped-def]
46 73 pass
47 74
48 def __enter__(self, *args, **kwargs):
75 def __enter__(self, *args, **kwargs): # type: ignore [no-untyped-def]
49 76 pass
50 77
51 def __exit__(self, *args, **kwargs):
78 def __exit__(self, *args, **kwargs): # type: ignore [no-untyped-def]
52 79 pass
53 80
54 81
55 82 @decorator
56 def only_when_enabled(f, self, *a, **kw):
83 def only_when_enabled(f, self, *a, **kw): # type: ignore [no-untyped-def]
57 84 """Decorator: return an empty list in the absence of sqlite."""
58 85 if not self.enabled:
59 86 return []
@@ -66,7 +93,7 def only_when_enabled(f, self, *a, **kw):
66 93 _SAVE_DB_SIZE = 16384
67 94
68 95 @decorator
69 def catch_corrupt_db(f, self, *a, **kw):
96 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
70 97 """A decorator which wraps HistoryAccessor method calls to catch errors from
71 98 a corrupt SQLite database, move the old database out of the way, and create
72 99 a new one.
@@ -76,7 +103,7 def catch_corrupt_db(f, self, *a, **kw):
76 103 """
77 104 try:
78 105 return f(self, *a, **kw)
79 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
106 except (DatabaseError, OperationalError) as e:
80 107 self._corrupt_db_counter += 1
81 108 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
82 109 if self.hist_file != ':memory:':
@@ -114,17 +141,39 def catch_corrupt_db(f, self, *a, **kw):
114 141 class HistoryAccessorBase(LoggingConfigurable):
115 142 """An abstract class for History Accessors """
116 143
117 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
144 def get_tail(
145 self,
146 n: int = 10,
147 raw: bool = True,
148 output: bool = False,
149 include_latest: bool = False,
150 ) -> Iterable[Tuple[int, int, InOrInOut]]:
118 151 raise NotImplementedError
119 152
120 def search(self, pattern="*", raw=True, search_raw=True,
121 output=False, n=None, unique=False):
153 def search(
154 self,
155 pattern: str = "*",
156 raw: bool = True,
157 search_raw: bool = True,
158 output: bool = False,
159 n: Optional[int] = None,
160 unique: bool = False,
161 ) -> Iterable[Tuple[int, int, InOrInOut]]:
122 162 raise NotImplementedError
123 163
124 def get_range(self, session, start=1, stop=None, raw=True,output=False):
164 def get_range(
165 self,
166 session: int,
167 start: int = 1,
168 stop: Optional[int] = None,
169 raw: bool = True,
170 output: bool = False,
171 ) -> Iterable[Tuple[int, int, InOrInOut]]:
125 172 raise NotImplementedError
126 173
127 def get_range_by_str(self, rangestr, raw=True, output=False):
174 def get_range_by_str(
175 self, rangestr: str, raw: bool = True, output: bool = False
176 ) -> Iterable[Tuple[int, int, InOrInOut]]:
128 177 raise NotImplementedError
129 178
130 179
@@ -160,7 +209,8 class HistoryAccessor(HistoryAccessorBase):
160 209 """,
161 210 ).tag(config=True)
162 211
163 enabled = Bool(True,
212 enabled = Bool(
213 sqlite3_found,
164 214 help="""enable the SQLite history
165 215
166 216 set enabled=False to disable the SQLite history,
@@ -179,13 +229,14 class HistoryAccessor(HistoryAccessorBase):
179 229 ).tag(config=True)
180 230
181 231 @default("connection_options")
182 def _default_connection_options(self):
232 def _default_connection_options(self) -> typing.Dict[str, bool]:
183 233 return dict(check_same_thread=False)
184 234
185 235 # The SQLite database
186 236 db = Any()
187 237 @observe('db')
188 def _db_changed(self, change):
238 @only_when_enabled
239 def _db_changed(self, change): # type: ignore [no-untyped-def]
189 240 """validate the db, since it can be an Instance of two different types"""
190 241 new = change['new']
191 242 connection_types = (DummyDB, sqlite3.Connection)
@@ -194,7 +245,9 class HistoryAccessor(HistoryAccessorBase):
194 245 (self.__class__.__name__, new)
195 246 raise TraitError(msg)
196 247
197 def __init__(self, profile="default", hist_file="", **traits):
248 def __init__(
249 self, profile: str = "default", hist_file: str = "", **traits: typing.Any
250 ) -> None:
198 251 """Create a new history accessor.
199 252
200 253 Parameters
@@ -222,7 +275,7 class HistoryAccessor(HistoryAccessorBase):
222 275
223 276 self.init_db()
224 277
225 def _get_hist_file_name(self, profile='default'):
278 def _get_hist_file_name(self, profile: str = "default") -> Path:
226 279 """Find the history file for the given profile name.
227 280
228 281 This is overridden by the HistoryManager subclass, to use the shell's
@@ -236,7 +289,7 class HistoryAccessor(HistoryAccessorBase):
236 289 return Path(locate_profile(profile)) / "history.sqlite"
237 290
238 291 @catch_corrupt_db
239 def init_db(self):
292 def init_db(self) -> None:
240 293 """Connect to the database, and create tables if necessary."""
241 294 if not self.enabled:
242 295 self.db = DummyDB()
@@ -245,7 +298,7 class HistoryAccessor(HistoryAccessorBase):
245 298 # use detect_types so that timestamps return datetime objects
246 299 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
247 300 kwargs.update(self.connection_options)
248 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
301 self.db = sqlite3.connect(str(self.hist_file), **kwargs) # type: ignore [call-overload]
249 302 with self.db:
250 303 self.db.execute(
251 304 """CREATE TABLE IF NOT EXISTS sessions (session integer
@@ -267,7 +320,7 class HistoryAccessor(HistoryAccessorBase):
267 320 # success! reset corrupt db count
268 321 self._corrupt_db_counter = 0
269 322
270 def writeout_cache(self):
323 def writeout_cache(self) -> None:
271 324 """Overridden by HistoryManager to dump the cache before certain
272 325 database lookups."""
273 326 pass
@@ -275,7 +328,14 class HistoryAccessor(HistoryAccessorBase):
275 328 ## -------------------------------
276 329 ## Methods for retrieving history:
277 330 ## -------------------------------
278 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
331 def _run_sql(
332 self,
333 sql: str,
334 params: typing.Tuple,
335 raw: bool = True,
336 output: bool = False,
337 latest: bool = False,
338 ) -> Iterable[Tuple[int, int, InOrInOut]]:
279 339 """Prepares and runs an SQL query for the history database.
280 340
281 341 Parameters
@@ -310,7 +370,9 class HistoryAccessor(HistoryAccessorBase):
310 370
311 371 @only_when_enabled
312 372 @catch_corrupt_db
313 def get_session_info(self, session):
373 def get_session_info(
374 self, session: int
375 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
314 376 """Get info about a session.
315 377
316 378 Parameters
@@ -335,7 +397,7 class HistoryAccessor(HistoryAccessorBase):
335 397 return self.db.execute(query, (session,)).fetchone()
336 398
337 399 @catch_corrupt_db
338 def get_last_session_id(self):
400 def get_last_session_id(self) -> Optional[int]:
339 401 """Get the last session ID currently in the database.
340 402
341 403 Within IPython, this should be the same as the value stored in
@@ -343,9 +405,16 class HistoryAccessor(HistoryAccessorBase):
343 405 """
344 406 for record in self.get_tail(n=1, include_latest=True):
345 407 return record[0]
408 return None
346 409
347 410 @catch_corrupt_db
348 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
411 def get_tail(
412 self,
413 n: int = 10,
414 raw: bool = True,
415 output: bool = False,
416 include_latest: bool = False,
417 ) -> Iterable[Tuple[int, int, InOrInOut]]:
349 418 """Get the last n lines from the history database.
350 419
351 420 Parameters
@@ -374,8 +443,15 class HistoryAccessor(HistoryAccessorBase):
374 443 return reversed(list(cur))
375 444
376 445 @catch_corrupt_db
377 def search(self, pattern="*", raw=True, search_raw=True,
378 output=False, n=None, unique=False):
446 def search(
447 self,
448 pattern: str = "*",
449 raw: bool = True,
450 search_raw: bool = True,
451 output: bool = False,
452 n: Optional[int] = None,
453 unique: bool = False,
454 ) -> Iterable[Tuple[int, int, InOrInOut]]:
379 455 """Search the database using unix glob-style matching (wildcards
380 456 * and ?).
381 457
@@ -402,7 +478,7 class HistoryAccessor(HistoryAccessorBase):
402 478 tosearch = "history." + tosearch
403 479 self.writeout_cache()
404 480 sqlform = "WHERE %s GLOB ?" % tosearch
405 params = (pattern,)
481 params: typing.Tuple[typing.Any, ...] = (pattern,)
406 482 if unique:
407 483 sqlform += ' GROUP BY {0}'.format(tosearch)
408 484 if n is not None:
@@ -416,7 +492,14 class HistoryAccessor(HistoryAccessorBase):
416 492 return cur
417 493
418 494 @catch_corrupt_db
419 def get_range(self, session, start=1, stop=None, raw=True,output=False):
495 def get_range(
496 self,
497 session: int,
498 start: int = 1,
499 stop: Optional[int] = None,
500 raw: bool = True,
501 output: bool = False,
502 ) -> Iterable[Tuple[int, int, InOrInOut]]:
420 503 """Retrieve input by session.
421 504
422 505 Parameters
@@ -443,6 +526,7 class HistoryAccessor(HistoryAccessorBase):
443 526 (session, line, input) if output is False, or
444 527 (session, line, (input, output)) if output is True.
445 528 """
529 params: typing.Tuple[typing.Any, ...]
446 530 if stop:
447 531 lineclause = "line >= ? AND line < ?"
448 532 params = (session, start, stop)
@@ -453,7 +537,9 class HistoryAccessor(HistoryAccessorBase):
453 537 return self._run_sql("WHERE session==? AND %s" % lineclause,
454 538 params, raw=raw, output=output)
455 539
456 def get_range_by_str(self, rangestr, raw=True, output=False):
540 def get_range_by_str(
541 self, rangestr: str, raw: bool = True, output: bool = False
542 ) -> Iterable[Tuple[int, int, InOrInOut]]:
457 543 """Get lines of history from a string of ranges, as used by magic
458 544 commands %hist, %save, %macro, etc.
459 545
@@ -483,8 +569,9 class HistoryManager(HistoryAccessor):
483 569 # Public interface
484 570
485 571 # An instance of the IPython shell we are attached to
486 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
487 allow_none=True)
572 shell = Instance(
573 "IPython.core.interactiveshell.InteractiveShellABC", allow_none=False
574 )
488 575 # Lists to hold processed and raw history. These start with a blank entry
489 576 # so that we can index them starting from 1
490 577 input_hist_parsed = List([""])
@@ -493,7 +580,7 class HistoryManager(HistoryAccessor):
493 580 dir_hist: List = List()
494 581
495 582 @default("dir_hist")
496 def _dir_hist_default(self):
583 def _dir_hist_default(self) -> typing.List[Path]:
497 584 try:
498 585 return [Path.cwd()]
499 586 except OSError:
@@ -503,10 +590,10 class HistoryManager(HistoryAccessor):
503 590 # execution count.
504 591 output_hist = Dict()
505 592 # The text/plain repr of outputs.
506 output_hist_reprs = Dict()
593 output_hist_reprs: typing.Dict[int, str] = Dict() # type: ignore [assignment]
507 594
508 595 # The number of the current session in the history database
509 session_number = Integer()
596 session_number: int = Integer() # type: ignore [assignment]
510 597
511 598 db_log_output = Bool(False,
512 599 help="Should the history database include output? (default: no)"
@@ -516,13 +603,13 class HistoryManager(HistoryAccessor):
516 603 "Values of 1 or less effectively disable caching."
517 604 ).tag(config=True)
518 605 # The input and output caches
519 db_input_cache: List = List()
520 db_output_cache: List = List()
606 db_input_cache: List[Tuple[int, str, str]] = List()
607 db_output_cache: List[Tuple[int, str]] = List()
521 608
522 609 # History saving in separate thread
523 610 save_thread = Instance('IPython.core.history.HistorySavingThread',
524 611 allow_none=True)
525 save_flag = Instance(threading.Event, allow_none=True)
612 save_flag = Instance(threading.Event, allow_none=False)
526 613
527 614 # Private interface
528 615 # Variables used to store the three last inputs from the user. On each new
@@ -538,18 +625,21 class HistoryManager(HistoryAccessor):
538 625 # an exit call).
539 626 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
540 627
541 def __init__(self, shell=None, config=None, **traits):
542 """Create a new history manager associated with a shell instance.
543 """
544 super(HistoryManager, self).__init__(shell=shell, config=config,
545 **traits)
628 def __init__(
629 self,
630 shell: InteractiveShell,
631 config: Optional[Configuration] = None,
632 **traits: typing.Any,
633 ):
634 """Create a new history manager associated with a shell instance."""
635 super().__init__(shell=shell, config=config, **traits)
546 636 self.save_flag = threading.Event()
547 637 self.db_input_cache_lock = threading.Lock()
548 638 self.db_output_cache_lock = threading.Lock()
549 639
550 640 try:
551 641 self.new_session()
552 except sqlite3.OperationalError:
642 except OperationalError:
553 643 self.log.error("Failed to create history session in %s. History will not be saved.",
554 644 self.hist_file, exc_info=True)
555 645 self.hist_file = ':memory:'
@@ -565,7 +655,7 class HistoryManager(HistoryAccessor):
565 655 )
566 656 self.hist_file = ":memory:"
567 657
568 def _get_hist_file_name(self, profile=None):
658 def _get_hist_file_name(self, profile: Optional[str] = None) -> Path:
569 659 """Get default history file name based on the Shell's profile.
570 660
571 661 The profile parameter is ignored, but must exist for compatibility with
@@ -574,7 +664,7 class HistoryManager(HistoryAccessor):
574 664 return Path(profile_dir) / "history.sqlite"
575 665
576 666 @only_when_enabled
577 def new_session(self, conn=None):
667 def new_session(self, conn: Optional[sqlite3.Connection] = None) -> None:
578 668 """Get a new session number."""
579 669 if conn is None:
580 670 conn = self.db
@@ -585,9 +675,10 class HistoryManager(HistoryAccessor):
585 675 NULL, '') """,
586 676 (datetime.datetime.now().isoformat(" "),),
587 677 )
678 assert isinstance(cur.lastrowid, int)
588 679 self.session_number = cur.lastrowid
589 680
590 def end_session(self):
681 def end_session(self) -> None:
591 682 """Close the database session, filling in the end time and line count."""
592 683 self.writeout_cache()
593 684 with self.db:
@@ -602,13 +693,13 class HistoryManager(HistoryAccessor):
602 693 )
603 694 self.session_number = 0
604 695
605 def name_session(self, name):
696 def name_session(self, name: str) -> None:
606 697 """Give the current session a name in the history database."""
607 698 with self.db:
608 699 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
609 700 (name, self.session_number))
610 701
611 def reset(self, new_session=True):
702 def reset(self, new_session: bool = True) -> None:
612 703 """Clear the session history, releasing all object references, and
613 704 optionally open a new session."""
614 705 self.output_hist.clear()
@@ -625,7 +716,9 class HistoryManager(HistoryAccessor):
625 716 # ------------------------------
626 717 # Methods for retrieving history
627 718 # ------------------------------
628 def get_session_info(self, session=0):
719 def get_session_info(
720 self, session: int = 0
721 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
629 722 """Get info about a session.
630 723
631 724 Parameters
@@ -653,7 +746,13 class HistoryManager(HistoryAccessor):
653 746 return super(HistoryManager, self).get_session_info(session=session)
654 747
655 748 @catch_corrupt_db
656 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
749 def get_tail(
750 self,
751 n: int = 10,
752 raw: bool = True,
753 output: bool = False,
754 include_latest: bool = False,
755 ) -> Iterable[Tuple[int, int, InOrInOut]]:
657 756 """Get the last n lines from the history database.
658 757
659 758 Most recent entry last.
@@ -697,7 +796,7 class HistoryManager(HistoryAccessor):
697 796 )
698 797 )
699 798
700 everything = this_cur + other_cur
799 everything: typing.List[Tuple[int, int, InOrInOut]] = this_cur + other_cur
701 800
702 801 everything = everything[:n]
703 802
@@ -705,7 +804,13 class HistoryManager(HistoryAccessor):
705 804 return list(everything)[:0:-1]
706 805 return list(everything)[::-1]
707 806
708 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
807 def _get_range_session(
808 self,
809 start: int = 1,
810 stop: Optional[int] = None,
811 raw: bool = True,
812 output: bool = False,
813 ) -> Iterable[Tuple[int, int, InOrInOut]]:
709 814 """Get input and output history from the current session. Called by
710 815 get_range, and takes similar parameters."""
711 816 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
@@ -717,7 +822,7 class HistoryManager(HistoryAccessor):
717 822 stop = n
718 823 elif stop < 0:
719 824 stop += n
720
825 line: InOrInOut
721 826 for i in range(start, stop):
722 827 if output:
723 828 line = (input_hist[i], self.output_hist_reprs.get(i))
@@ -725,7 +830,14 class HistoryManager(HistoryAccessor):
725 830 line = input_hist[i]
726 831 yield (0, i, line)
727 832
728 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
833 def get_range(
834 self,
835 session: int = 0,
836 start: int = 1,
837 stop: Optional[int] = None,
838 raw: bool = True,
839 output: bool = False,
840 ) -> Iterable[Tuple[int, int, InOrInOut]]:
729 841 """Retrieve input by session.
730 842
731 843 Parameters
@@ -763,7 +875,9 class HistoryManager(HistoryAccessor):
763 875 ## ----------------------------
764 876 ## Methods for storing history:
765 877 ## ----------------------------
766 def store_inputs(self, line_num, source, source_raw=None):
878 def store_inputs(
879 self, line_num: int, source: str, source_raw: Optional[str] = None
880 ) -> None:
767 881 """Store source and raw input in history and create input cache
768 882 variables ``_i*``.
769 883
@@ -811,7 +925,7 class HistoryManager(HistoryAccessor):
811 925 if self.shell is not None:
812 926 self.shell.push(to_main, interactive=False)
813 927
814 def store_output(self, line_num):
928 def store_output(self, line_num: int) -> None:
815 929 """If database output logging is enabled, this saves all the
816 930 outputs from the indicated prompt number to the database. It's
817 931 called by run_cell after code has been executed.
@@ -823,6 +937,7 class HistoryManager(HistoryAccessor):
823 937 """
824 938 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
825 939 return
940 lnum: int = line_num
826 941 output = self.output_hist_reprs[line_num]
827 942
828 943 with self.db_output_cache_lock:
@@ -830,20 +945,20 class HistoryManager(HistoryAccessor):
830 945 if self.db_cache_size <= 1:
831 946 self.save_flag.set()
832 947
833 def _writeout_input_cache(self, conn):
948 def _writeout_input_cache(self, conn: sqlite3.Connection) -> None:
834 949 with conn:
835 950 for line in self.db_input_cache:
836 951 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
837 952 (self.session_number,)+line)
838 953
839 def _writeout_output_cache(self, conn):
954 def _writeout_output_cache(self, conn: sqlite3.Connection) -> None:
840 955 with conn:
841 956 for line in self.db_output_cache:
842 957 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
843 958 (self.session_number,)+line)
844 959
845 960 @only_when_enabled
846 def writeout_cache(self, conn=None):
961 def writeout_cache(self, conn: Optional[sqlite3.Connection] = None) -> None:
847 962 """Write any entries in the cache to the database."""
848 963 if conn is None:
849 964 conn = self.db
@@ -885,13 +1000,15 class HistorySavingThread(threading.Thread):
885 1000 daemon = True
886 1001 stop_now = False
887 1002 enabled = True
888 def __init__(self, history_manager):
1003 history_manager: HistoryManager
1004
1005 def __init__(self, history_manager: HistoryManager) -> None:
889 1006 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
890 1007 self.history_manager = history_manager
891 1008 self.enabled = history_manager.enabled
892 1009
893 1010 @only_when_enabled
894 def run(self):
1011 def run(self) -> None:
895 1012 atexit.register(self.stop)
896 1013 # We need a separate db connection per thread:
897 1014 try:
@@ -912,7 +1029,7 class HistorySavingThread(threading.Thread):
912 1029 finally:
913 1030 atexit.unregister(self.stop)
914 1031
915 def stop(self):
1032 def stop(self) -> None:
916 1033 """This can be called from the main thread to safely stop this thread.
917 1034
918 1035 Note that it does not attempt to write out remaining history before
@@ -933,7 +1050,7 range_re = re.compile(r"""
933 1050 $""", re.VERBOSE)
934 1051
935 1052
936 def extract_hist_ranges(ranges_str):
1053 def extract_hist_ranges(ranges_str: str) -> Iterable[Tuple[int, int, Optional[int]]]:
937 1054 """Turn a string of history ranges into 3-tuples of (session, start, stop).
938 1055
939 1056 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
@@ -965,6 +1082,7 def extract_hist_ranges(ranges_str):
965 1082 end = None # provide the entire session hist
966 1083
967 1084 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
1085 assert end is not None
968 1086 end += 1
969 1087 startsess = rmatch.group("startsess") or "0"
970 1088 endsess = rmatch.group("endsess") or startsess
@@ -982,7 +1100,7 def extract_hist_ranges(ranges_str):
982 1100 yield (endsess, 1, end)
983 1101
984 1102
985 def _format_lineno(session, line):
1103 def _format_lineno(session: int, line: int) -> str:
986 1104 """Helper function to format line numbers properly."""
987 1105 if session == 0:
988 1106 return str(line)
@@ -199,7 +199,6 module = [
199 199 "IPython.core.formatters",
200 200 "IPython.core.getipython",
201 201 "IPython.core.guarded_eval",
202 "IPython.core.history",
203 202 "IPython.core.historyapp",
204 203 "IPython.core.hooks",
205 204 "IPython.core.inputtransformer",
General Comments 0
You need to be logged in to leave comments. Login now