##// END OF EJS Templates
Add more type in history.py (#14616)...
M Bussonnier -
r29019:89d672ee merge
parent child Browse files
Show More
@@ -1,989 +1,1107
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
6 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
14 17 from decorator import decorator
15 18 from traitlets import (
16 19 Any,
17 20 Bool,
18 21 Dict,
19 22 Instance,
20 23 Integer,
21 24 List,
22 25 TraitError,
23 26 Unicode,
24 27 Union,
25 28 default,
26 29 observe,
27 30 )
28 31 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 []
60 87 else:
61 88 return f(self, *a, **kw)
62 89
63 90
64 91 # use 16kB as threshold for whether a corrupt history db should be saved
65 92 # that should be at least 100 entries or so
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.
73 100
74 101 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
75 102 not just a corrupt file.
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:':
83 110 if self._corrupt_db_counter > self._corrupt_db_limit:
84 111 self.hist_file = ':memory:'
85 112 self.log.error("Failed to load history too many times, history will not be saved.")
86 113 elif self.hist_file.is_file():
87 114 # move the file out of the way
88 115 base = str(self.hist_file.parent / self.hist_file.stem)
89 116 ext = self.hist_file.suffix
90 117 size = self.hist_file.stat().st_size
91 118 if size >= _SAVE_DB_SIZE:
92 119 # if there's significant content, avoid clobbering
93 120 now = datetime.datetime.now().isoformat().replace(':', '.')
94 121 newpath = base + '-corrupt-' + now + ext
95 122 # don't clobber previous corrupt backups
96 123 for i in range(100):
97 124 if not Path(newpath).exists():
98 125 break
99 126 else:
100 127 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
101 128 else:
102 129 # not much content, possibly empty; don't worry about clobbering
103 130 # maybe we should just delete it?
104 131 newpath = base + '-corrupt' + ext
105 132 self.hist_file.rename(newpath)
106 133 self.log.error("History file was moved to %s and a new file created.", newpath)
107 134 self.init_db()
108 135 return []
109 136 else:
110 137 # Failed with :memory:, something serious is wrong
111 138 raise
112 139
113 140
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
131 180 class HistoryAccessor(HistoryAccessorBase):
132 181 """Access the history database without adding to it.
133 182
134 183 This is intended for use by standalone history tools. IPython shells use
135 184 HistoryManager, below, which is a subclass of this."""
136 185
137 186 # counter for init_db retries, so we don't keep trying over and over
138 187 _corrupt_db_counter = 0
139 188 # after two failures, fallback on :memory:
140 189 _corrupt_db_limit = 2
141 190
142 191 # String holding the path to the history file
143 192 hist_file = Union(
144 193 [Instance(Path), Unicode()],
145 194 help="""Path to file to use for SQLite history database.
146 195
147 196 By default, IPython will put the history database in the IPython
148 197 profile directory. If you would rather share one history among
149 198 profiles, you can set this value in each, so that they are consistent.
150 199
151 200 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
152 201 mounts. If you see IPython hanging, try setting this to something on a
153 202 local disk, e.g::
154 203
155 204 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
156 205
157 206 you can also use the specific value `:memory:` (including the colon
158 207 at both end but not the back ticks), to avoid creating an history file.
159 208
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,
167 217 in which case there will be no stored history, no SQLite connection,
168 218 and no background saving thread. This may be necessary in some
169 219 threaded environments where IPython is embedded.
170 220 """,
171 221 ).tag(config=True)
172 222
173 223 connection_options = Dict(
174 224 help="""Options for configuring the SQLite connection
175 225
176 226 These options are passed as keyword args to sqlite3.connect
177 227 when establishing database connections.
178 228 """
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)
192 243 if not isinstance(new, connection_types):
193 244 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
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
201 254 ----------
202 255 profile : str
203 256 The name of the profile from which to open history.
204 257 hist_file : str
205 258 Path to an SQLite history database stored by IPython. If specified,
206 259 hist_file overrides profile.
207 260 config : :class:`~traitlets.config.loader.Config`
208 261 Config object. hist_file can also be set through this.
209 262 """
210 263 super(HistoryAccessor, self).__init__(**traits)
211 264 # defer setting hist_file from kwarg until after init,
212 265 # otherwise the default kwarg value would clobber any value
213 266 # set by config
214 267 if hist_file:
215 268 self.hist_file = hist_file
216 269
217 270 try:
218 271 self.hist_file
219 272 except TraitError:
220 273 # No one has set the hist_file, yet.
221 274 self.hist_file = self._get_hist_file_name(profile)
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
229 282 active profile.
230 283
231 284 Parameters
232 285 ----------
233 286 profile : str
234 287 The name of a profile which has a history file.
235 288 """
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()
243 296 return
244 297
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
252 305 primary key autoincrement, start timestamp,
253 306 end timestamp, num_cmds integer, remark text)"""
254 307 )
255 308 self.db.execute(
256 309 """CREATE TABLE IF NOT EXISTS history
257 310 (session integer, line integer, source text, source_raw text,
258 311 PRIMARY KEY (session, line))"""
259 312 )
260 313 # Output history is optional, but ensure the table's there so it can be
261 314 # enabled later.
262 315 self.db.execute(
263 316 """CREATE TABLE IF NOT EXISTS output_history
264 317 (session integer, line integer, output text,
265 318 PRIMARY KEY (session, line))"""
266 319 )
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
274 327
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
282 342 ----------
283 343 sql : str
284 344 Any filtering expressions to go after SELECT ... FROM ...
285 345 params : tuple
286 346 Parameters passed to the SQL query (to replace "?")
287 347 raw, output : bool
288 348 See :meth:`get_range`
289 349 latest : bool
290 350 Select rows with max (session, line)
291 351
292 352 Returns
293 353 -------
294 354 Tuples as :meth:`get_range`
295 355 """
296 356 toget = 'source_raw' if raw else 'source'
297 357 sqlfrom = "history"
298 358 if output:
299 359 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
300 360 toget = "history.%s, output_history.output" % toget
301 361 if latest:
302 362 toget += ", MAX(session * 128 * 1024 + line)"
303 363 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
304 364 cur = self.db.execute(this_querry, params)
305 365 if latest:
306 366 cur = (row[:-1] for row in cur)
307 367 if output: # Regroup into 3-tuples, and parse JSON
308 368 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
309 369 return cur
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
317 379 ----------
318 380 session : int
319 381 Session number to retrieve.
320 382
321 383 Returns
322 384 -------
323 385 session_id : int
324 386 Session ID number
325 387 start : datetime
326 388 Timestamp for the start of the session.
327 389 end : datetime
328 390 Timestamp for the end of the session, or None if IPython crashed.
329 391 num_cmds : int
330 392 Number of commands run, or None if IPython crashed.
331 393 remark : unicode
332 394 A manually set description.
333 395 """
334 396 query = "SELECT * from sessions where session == ?"
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
342 404 :attr:`HistoryManager.session_number`.
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
352 421 ----------
353 422 n : int
354 423 The number of lines to get
355 424 raw, output : bool
356 425 See :meth:`get_range`
357 426 include_latest : bool
358 427 If False (default), n+1 lines are fetched, and the latest one
359 428 is discarded. This is intended to be used where the function
360 429 is called by a user command, which it should not return.
361 430
362 431 Returns
363 432 -------
364 433 Tuples as :meth:`get_range`
365 434 """
366 435 self.writeout_cache()
367 436 if not include_latest:
368 437 n += 1
369 438 cur = self._run_sql(
370 439 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
371 440 )
372 441 if not include_latest:
373 442 return reversed(list(cur)[1:])
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
382 458 Parameters
383 459 ----------
384 460 pattern : str
385 461 The wildcarded pattern to match when searching
386 462 search_raw : bool
387 463 If True, search the raw input, otherwise, the parsed input
388 464 raw, output : bool
389 465 See :meth:`get_range`
390 466 n : None or int
391 467 If an integer is given, it defines the limit of
392 468 returned entries.
393 469 unique : bool
394 470 When it is true, return only unique entries.
395 471
396 472 Returns
397 473 -------
398 474 Tuples as :meth:`get_range`
399 475 """
400 476 tosearch = "source_raw" if search_raw else "source"
401 477 if output:
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:
409 485 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
410 486 params += (n,)
411 487 elif unique:
412 488 sqlform += " ORDER BY session, line"
413 489 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
414 490 if n is not None:
415 491 return reversed(list(cur))
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
423 506 ----------
424 507 session : int
425 508 Session number to retrieve.
426 509 start : int
427 510 First line to retrieve.
428 511 stop : int
429 512 End of line range (excluded from output itself). If None, retrieve
430 513 to the end of the session.
431 514 raw : bool
432 515 If True, return untranslated input
433 516 output : bool
434 517 If True, attempt to include output. This will be 'real' Python
435 518 objects for the current session, or text reprs from previous
436 519 sessions if db_log_output was enabled at the time. Where no output
437 520 is found, None is used.
438 521
439 522 Returns
440 523 -------
441 524 entries
442 525 An iterator over the desired lines. Each line is a 3-tuple, either
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)
449 533 else:
450 534 lineclause = "line>=?"
451 535 params = (session, start)
452 536
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
460 546 Parameters
461 547 ----------
462 548 rangestr : str
463 549 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
464 550 this will return everything from current session's history.
465 551
466 552 See the documentation of :func:`%history` for the full details.
467 553
468 554 raw, output : bool
469 555 As :meth:`get_range`
470 556
471 557 Returns
472 558 -------
473 559 Tuples as :meth:`get_range`
474 560 """
475 561 for sess, s, e in extract_hist_ranges(rangestr):
476 562 for line in self.get_range(sess, s, e, raw=raw, output=output):
477 563 yield line
478 564
479 565
480 566 class HistoryManager(HistoryAccessor):
481 567 """A class to organize all history-related functionality in one place.
482 568 """
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([""])
491 578 input_hist_raw = List([""])
492 579 # A list of directories visited during session
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:
500 587 return []
501 588
502 589 # A dict of output history, keyed with ints from the shell's
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)"
513 600 ).tag(config=True)
514 601 db_cache_size = Integer(0,
515 602 help="Write to database every x commands (higher values save disk access & power).\n"
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
529 616 # history update, we populate the user's namespace with these, shifted as
530 617 # necessary.
531 618 _i00 = Unicode("")
532 619 _i = Unicode("")
533 620 _ii = Unicode("")
534 621 _iii = Unicode("")
535 622
536 623 # A regex matching all forms of the exit command, so that we don't store
537 624 # them in the history (it's annoying to rewind the first entry and land on
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:'
556 646
557 647 if self.enabled and self.hist_file != ':memory:':
558 648 self.save_thread = HistorySavingThread(self)
559 649 try:
560 650 self.save_thread.start()
561 651 except RuntimeError:
562 652 self.log.error(
563 653 "Failed to start history saving thread. History will not be saved.",
564 654 exc_info=True,
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
572 662 the parent class."""
573 663 profile_dir = self.shell.profile_dir.location
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
581 671
582 672 with conn:
583 673 cur = conn.execute(
584 674 """INSERT INTO sessions VALUES (NULL, ?, NULL,
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:
594 685 self.db.execute(
595 686 """UPDATE sessions SET end=?, num_cmds=? WHERE
596 687 session==?""",
597 688 (
598 689 datetime.datetime.now().isoformat(" "),
599 690 len(self.input_hist_parsed) - 1,
600 691 self.session_number,
601 692 ),
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()
615 706 # The directory history can't be completely empty
616 707 self.dir_hist[:] = [Path.cwd()]
617 708
618 709 if new_session:
619 710 if self.session_number:
620 711 self.end_session()
621 712 self.input_hist_parsed[:] = [""]
622 713 self.input_hist_raw[:] = [""]
623 714 self.new_session()
624 715
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
632 725 ----------
633 726 session : int
634 727 Session number to retrieve. The current session is 0, and negative
635 728 numbers count back from current session, so -1 is the previous session.
636 729
637 730 Returns
638 731 -------
639 732 session_id : int
640 733 Session ID number
641 734 start : datetime
642 735 Timestamp for the start of the session.
643 736 end : datetime
644 737 Timestamp for the end of the session, or None if IPython crashed.
645 738 num_cmds : int
646 739 Number of commands run, or None if IPython crashed.
647 740 remark : unicode
648 741 A manually set description.
649 742 """
650 743 if session <= 0:
651 744 session += self.session_number
652 745
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.
660 759
661 760 Completion will be reordered so that that the last ones are when
662 761 possible from current session.
663 762
664 763 Parameters
665 764 ----------
666 765 n : int
667 766 The number of lines to get
668 767 raw, output : bool
669 768 See :meth:`get_range`
670 769 include_latest : bool
671 770 If False (default), n+1 lines are fetched, and the latest one
672 771 is discarded. This is intended to be used where the function
673 772 is called by a user command, which it should not return.
674 773
675 774 Returns
676 775 -------
677 776 Tuples as :meth:`get_range`
678 777 """
679 778 self.writeout_cache()
680 779 if not include_latest:
681 780 n += 1
682 781 # cursor/line/entry
683 782 this_cur = list(
684 783 self._run_sql(
685 784 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
686 785 (self.session_number, n),
687 786 raw=raw,
688 787 output=output,
689 788 )
690 789 )
691 790 other_cur = list(
692 791 self._run_sql(
693 792 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
694 793 (self.session_number, n),
695 794 raw=raw,
696 795 output=output,
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
704 803 if not include_latest:
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
712 817
713 818 n = len(input_hist)
714 819 if start < 0:
715 820 start += n
716 821 if not stop or (stop > n):
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))
724 829 else:
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
732 844 ----------
733 845 session : int
734 846 Session number to retrieve. The current session is 0, and negative
735 847 numbers count back from current session, so -1 is previous session.
736 848 start : int
737 849 First line to retrieve.
738 850 stop : int
739 851 End of line range (excluded from output itself). If None, retrieve
740 852 to the end of the session.
741 853 raw : bool
742 854 If True, return untranslated input
743 855 output : bool
744 856 If True, attempt to include output. This will be 'real' Python
745 857 objects for the current session, or text reprs from previous
746 858 sessions if db_log_output was enabled at the time. Where no output
747 859 is found, None is used.
748 860
749 861 Returns
750 862 -------
751 863 entries
752 864 An iterator over the desired lines. Each line is a 3-tuple, either
753 865 (session, line, input) if output is False, or
754 866 (session, line, (input, output)) if output is True.
755 867 """
756 868 if session <= 0:
757 869 session += self.session_number
758 870 if session==self.session_number: # Current session
759 871 return self._get_range_session(start, stop, raw, output)
760 872 return super(HistoryManager, self).get_range(session, start, stop, raw,
761 873 output)
762 874
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
770 884 Parameters
771 885 ----------
772 886 line_num : int
773 887 The prompt number of this input.
774 888 source : str
775 889 Python input.
776 890 source_raw : str, optional
777 891 If given, this is the raw input without any IPython transformations
778 892 applied to it. If not given, ``source`` is used.
779 893 """
780 894 if source_raw is None:
781 895 source_raw = source
782 896 source = source.rstrip('\n')
783 897 source_raw = source_raw.rstrip('\n')
784 898
785 899 # do not store exit/quit commands
786 900 if self._exit_re.match(source_raw.strip()):
787 901 return
788 902
789 903 self.input_hist_parsed.append(source)
790 904 self.input_hist_raw.append(source_raw)
791 905
792 906 with self.db_input_cache_lock:
793 907 self.db_input_cache.append((line_num, source, source_raw))
794 908 # Trigger to flush cache and write to DB.
795 909 if len(self.db_input_cache) >= self.db_cache_size:
796 910 self.save_flag.set()
797 911
798 912 # update the auto _i variables
799 913 self._iii = self._ii
800 914 self._ii = self._i
801 915 self._i = self._i00
802 916 self._i00 = source_raw
803 917
804 918 # hackish access to user namespace to create _i1,_i2... dynamically
805 919 new_i = '_i%s' % line_num
806 920 to_main = {'_i': self._i,
807 921 '_ii': self._ii,
808 922 '_iii': self._iii,
809 923 new_i : self._i00 }
810 924
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.
818 932
819 933 Parameters
820 934 ----------
821 935 line_num : int
822 936 The line number from which to save outputs
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:
829 944 self.db_output_cache.append((line_num, output))
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
850 965
851 966 with self.db_input_cache_lock:
852 967 try:
853 968 self._writeout_input_cache(conn)
854 969 except sqlite3.IntegrityError:
855 970 self.new_session(conn)
856 971 print("ERROR! Session/line number was not unique in",
857 972 "database. History logging moved to new session",
858 973 self.session_number)
859 974 try:
860 975 # Try writing to the new session. If this fails, don't
861 976 # recurse
862 977 self._writeout_input_cache(conn)
863 978 except sqlite3.IntegrityError:
864 979 pass
865 980 finally:
866 981 self.db_input_cache = []
867 982
868 983 with self.db_output_cache_lock:
869 984 try:
870 985 self._writeout_output_cache(conn)
871 986 except sqlite3.IntegrityError:
872 987 print("!! Session/line number for output was not unique",
873 988 "in database. Output will not be stored.")
874 989 finally:
875 990 self.db_output_cache = []
876 991
877 992
878 993 class HistorySavingThread(threading.Thread):
879 994 """This thread takes care of writing history to the database, so that
880 995 the UI isn't held up while that happens.
881 996
882 997 It waits for the HistoryManager's save_flag to be set, then writes out
883 998 the history cache. The main thread is responsible for setting the flag when
884 999 the cache size reaches a defined threshold."""
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:
898 1015 self.db = sqlite3.connect(
899 1016 str(self.history_manager.hist_file),
900 1017 **self.history_manager.connection_options,
901 1018 )
902 1019 while True:
903 1020 self.history_manager.save_flag.wait()
904 1021 if self.stop_now:
905 1022 self.db.close()
906 1023 return
907 1024 self.history_manager.save_flag.clear()
908 1025 self.history_manager.writeout_cache(self.db)
909 1026 except Exception as e:
910 1027 print(("The history saving thread hit an unexpected error (%s)."
911 1028 "History will not be written to the database.") % repr(e))
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
919 1036 exiting. That should be done by calling the HistoryManager's
920 1037 end_session method."""
921 1038 self.stop_now = True
922 1039 self.history_manager.save_flag.set()
923 1040 self.join()
924 1041
925 1042
926 1043 # To match, e.g. ~5/8-~2/3
927 1044 range_re = re.compile(r"""
928 1045 ((?P<startsess>~?\d+)/)?
929 1046 (?P<start>\d+)?
930 1047 ((?P<sep>[\-:])
931 1048 ((?P<endsess>~?\d+)/)?
932 1049 (?P<end>\d+))?
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
940 1057 session".
941 1058
942 1059 Examples
943 1060 --------
944 1061 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
945 1062 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
946 1063 """
947 1064 if ranges_str == "":
948 1065 yield (0, 1, None) # Everything from current session
949 1066 return
950 1067
951 1068 for range_str in ranges_str.split():
952 1069 rmatch = range_re.match(range_str)
953 1070 if not rmatch:
954 1071 continue
955 1072 start = rmatch.group("start")
956 1073 if start:
957 1074 start = int(start)
958 1075 end = rmatch.group("end")
959 1076 # If no end specified, get (a, a + 1)
960 1077 end = int(end) if end else start + 1
961 1078 else: # start not specified
962 1079 if not rmatch.group('startsess'): # no startsess
963 1080 continue
964 1081 start = 1
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
971 1089 startsess = int(startsess.replace("~","-"))
972 1090 endsess = int(endsess.replace("~","-"))
973 1091 assert endsess >= startsess, "start session must be earlier than end session"
974 1092
975 1093 if endsess == startsess:
976 1094 yield (startsess, start, end)
977 1095 continue
978 1096 # Multiple sessions in one range:
979 1097 yield (startsess, start, None)
980 1098 for sess in range(startsess+1, endsess):
981 1099 yield (sess, 1, None)
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)
989 1107 return "%s#%s" % (session, line)
@@ -1,388 +1,387
1 1 [build-system]
2 2 requires = ["setuptools>=61.2"]
3 3 # We need access to the 'setupbase' module at build time.
4 4 # Hence we declare a custom build backend.
5 5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
6 6 backend-path = ["."]
7 7
8 8 [project]
9 9 name = "ipython"
10 10 description = "IPython: Productive Interactive Computing"
11 11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
12 12 classifiers = [
13 13 "Framework :: IPython",
14 14 "Framework :: Jupyter",
15 15 "Intended Audience :: Developers",
16 16 "Intended Audience :: Science/Research",
17 17 "License :: OSI Approved :: BSD License",
18 18 "Programming Language :: Python",
19 19 "Programming Language :: Python :: 3",
20 20 "Programming Language :: Python :: 3 :: Only",
21 21 "Topic :: System :: Shells",
22 22 ]
23 23 requires-python = ">=3.11"
24 24 dependencies = [
25 25 'colorama; sys_platform == "win32"',
26 26 "decorator",
27 27 "jedi>=0.16",
28 28 "matplotlib-inline",
29 29 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
30 30 "prompt_toolkit>=3.0.41,<3.1.0",
31 31 "pygments>=2.4.0",
32 32 "stack_data",
33 33 "traitlets>=5.13.0",
34 34 "typing_extensions>=4.6; python_version<'3.12'",
35 35 ]
36 36 dynamic = ["authors", "license", "version"]
37 37
38 38 [project.entry-points."pygments.lexers"]
39 39 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
40 40 ipython = "IPython.lib.lexers:IPythonLexer"
41 41 ipython3 = "IPython.lib.lexers:IPython3Lexer"
42 42
43 43 [project.scripts]
44 44 ipython = "IPython:start_ipython"
45 45 ipython3 = "IPython:start_ipython"
46 46
47 47 [project.readme]
48 48 file = "long_description.rst"
49 49 content-type = "text/x-rst"
50 50
51 51 [project.urls]
52 52 Homepage = "https://ipython.org"
53 53 Documentation = "https://ipython.readthedocs.io/"
54 54 Funding = "https://numfocus.org/"
55 55 Source = "https://github.com/ipython/ipython"
56 56 Tracker = "https://github.com/ipython/ipython/issues"
57 57
58 58 [project.optional-dependencies]
59 59 black = [
60 60 "black",
61 61 ]
62 62 doc = [
63 63 "docrepr",
64 64 "exceptiongroup",
65 65 "intersphinx_registry",
66 66 "ipykernel",
67 67 "ipython[test]",
68 68 "matplotlib",
69 69 "setuptools>=18.5",
70 70 "sphinx-rtd-theme",
71 71 "sphinx>=1.3",
72 72 "sphinxcontrib-jquery",
73 73 ]
74 74 kernel = [
75 75 "ipykernel",
76 76 ]
77 77 nbconvert = [
78 78 "nbconvert",
79 79 ]
80 80 nbformat = [
81 81 "nbformat",
82 82 ]
83 83 notebook = [
84 84 "ipywidgets",
85 85 "notebook",
86 86 ]
87 87 parallel = [
88 88 "ipyparallel",
89 89 ]
90 90 qtconsole = [
91 91 "qtconsole",
92 92 ]
93 93 terminal = []
94 94 test = [
95 95 "pytest",
96 96 "pytest-asyncio<0.22",
97 97 "testpath",
98 98 "pickleshare",
99 99 "packaging",
100 100 ]
101 101 test_extra = [
102 102 "ipython[test]",
103 103 "curio",
104 104 "matplotlib!=3.2.0",
105 105 "nbformat",
106 106 "numpy>=1.23",
107 107 "pandas",
108 108 "trio",
109 109 ]
110 110 matplotlib = [
111 111 "matplotlib"
112 112 ]
113 113 all = [
114 114 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
115 115 "ipython[test,test_extra]",
116 116 ]
117 117
118 118 [tool.mypy]
119 119 python_version = "3.10"
120 120 ignore_missing_imports = true
121 121 follow_imports = 'silent'
122 122 exclude = [
123 123 'test_\.+\.py',
124 124 'IPython.utils.tests.test_wildcard',
125 125 'testing',
126 126 'tests',
127 127 'PyColorize.py',
128 128 '_process_win32_controller.py',
129 129 'IPython/core/application.py',
130 130 'IPython/core/profileapp.py',
131 131 'IPython/lib/deepreload.py',
132 132 'IPython/sphinxext/ipython_directive.py',
133 133 'IPython/terminal/ipapp.py',
134 134 'IPython/utils/_process_win32.py',
135 135 'IPython/utils/path.py',
136 136 ]
137 137 # check_untyped_defs = true
138 138 # disallow_untyped_calls = true
139 139 # disallow_untyped_decorators = true
140 140 # ignore_errors = false
141 141 # ignore_missing_imports = false
142 142 disallow_incomplete_defs = true
143 143 disallow_untyped_defs = true
144 144 warn_redundant_casts = true
145 145
146 146 [[tool.mypy.overrides]]
147 147 module = [
148 148 "IPython.core.crashhandler",
149 149 ]
150 150 check_untyped_defs = true
151 151 disallow_incomplete_defs = true
152 152 disallow_untyped_calls = true
153 153 disallow_untyped_decorators = true
154 154 disallow_untyped_defs = true
155 155 ignore_errors = false
156 156 ignore_missing_imports = false
157 157
158 158 [[tool.mypy.overrides]]
159 159 module = [
160 160 "IPython.utils.text",
161 161 ]
162 162 disallow_untyped_defs = true
163 163 check_untyped_defs = false
164 164 disallow_untyped_decorators = true
165 165
166 166 [[tool.mypy.overrides]]
167 167 module = [
168 168 ]
169 169 disallow_untyped_defs = false
170 170 ignore_errors = true
171 171 ignore_missing_imports = true
172 172 disallow_untyped_calls = false
173 173 disallow_incomplete_defs = false
174 174 check_untyped_defs = false
175 175 disallow_untyped_decorators = false
176 176
177 177
178 178 # gloabl ignore error
179 179 [[tool.mypy.overrides]]
180 180 module = [
181 181 "IPython",
182 182 "IPython.conftest",
183 183 "IPython.core.alias",
184 184 "IPython.core.async_helpers",
185 185 "IPython.core.autocall",
186 186 "IPython.core.builtin_trap",
187 187 "IPython.core.compilerop",
188 188 "IPython.core.completer",
189 189 "IPython.core.completerlib",
190 190 "IPython.core.debugger",
191 191 "IPython.core.display",
192 192 "IPython.core.display_functions",
193 193 "IPython.core.display_trap",
194 194 "IPython.core.displayhook",
195 195 "IPython.core.displaypub",
196 196 "IPython.core.events",
197 197 "IPython.core.excolors",
198 198 "IPython.core.extensions",
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",
206 205 "IPython.core.inputtransformer2",
207 206 "IPython.core.interactiveshell",
208 207 "IPython.core.logger",
209 208 "IPython.core.macro",
210 209 "IPython.core.magic",
211 210 "IPython.core.magic_arguments",
212 211 "IPython.core.magics.ast_mod",
213 212 "IPython.core.magics.auto",
214 213 "IPython.core.magics.basic",
215 214 "IPython.core.magics.code",
216 215 "IPython.core.magics.config",
217 216 "IPython.core.magics.display",
218 217 "IPython.core.magics.execution",
219 218 "IPython.core.magics.extension",
220 219 "IPython.core.magics.history",
221 220 "IPython.core.magics.logging",
222 221 "IPython.core.magics.namespace",
223 222 "IPython.core.magics.osm",
224 223 "IPython.core.magics.packaging",
225 224 "IPython.core.magics.pylab",
226 225 "IPython.core.magics.script",
227 226 "IPython.core.oinspect",
228 227 "IPython.core.page",
229 228 "IPython.core.payload",
230 229 "IPython.core.payloadpage",
231 230 "IPython.core.prefilter",
232 231 "IPython.core.profiledir",
233 232 "IPython.core.prompts",
234 233 "IPython.core.pylabtools",
235 234 "IPython.core.shellapp",
236 235 "IPython.core.splitinput",
237 236 "IPython.core.ultratb",
238 237 "IPython.extensions.autoreload",
239 238 "IPython.extensions.storemagic",
240 239 "IPython.external.qt_for_kernel",
241 240 "IPython.external.qt_loaders",
242 241 "IPython.lib.backgroundjobs",
243 242 "IPython.lib.clipboard",
244 243 "IPython.lib.demo",
245 244 "IPython.lib.display",
246 245 "IPython.lib.editorhooks",
247 246 "IPython.lib.guisupport",
248 247 "IPython.lib.latextools",
249 248 "IPython.lib.lexers",
250 249 "IPython.lib.pretty",
251 250 "IPython.paths",
252 251 "IPython.sphinxext.ipython_console_highlighting",
253 252 "IPython.terminal.debugger",
254 253 "IPython.terminal.embed",
255 254 "IPython.terminal.interactiveshell",
256 255 "IPython.terminal.magics",
257 256 "IPython.terminal.prompts",
258 257 "IPython.terminal.pt_inputhooks",
259 258 "IPython.terminal.pt_inputhooks.asyncio",
260 259 "IPython.terminal.pt_inputhooks.glut",
261 260 "IPython.terminal.pt_inputhooks.gtk",
262 261 "IPython.terminal.pt_inputhooks.gtk3",
263 262 "IPython.terminal.pt_inputhooks.gtk4",
264 263 "IPython.terminal.pt_inputhooks.osx",
265 264 "IPython.terminal.pt_inputhooks.pyglet",
266 265 "IPython.terminal.pt_inputhooks.qt",
267 266 "IPython.terminal.pt_inputhooks.tk",
268 267 "IPython.terminal.pt_inputhooks.wx",
269 268 "IPython.terminal.ptutils",
270 269 "IPython.terminal.shortcuts",
271 270 "IPython.terminal.shortcuts.auto_match",
272 271 "IPython.terminal.shortcuts.auto_suggest",
273 272 "IPython.terminal.shortcuts.filters",
274 273 "IPython.utils._process_cli",
275 274 "IPython.utils._process_common",
276 275 "IPython.utils._process_emscripten",
277 276 "IPython.utils._process_posix",
278 277 "IPython.utils.capture",
279 278 "IPython.utils.coloransi",
280 279 "IPython.utils.contexts",
281 280 "IPython.utils.data",
282 281 "IPython.utils.decorators",
283 282 "IPython.utils.dir2",
284 283 "IPython.utils.encoding",
285 284 "IPython.utils.frame",
286 285 "IPython.utils.generics",
287 286 "IPython.utils.importstring",
288 287 "IPython.utils.io",
289 288 "IPython.utils.ipstruct",
290 289 "IPython.utils.module_paths",
291 290 "IPython.utils.openpy",
292 291 "IPython.utils.process",
293 292 "IPython.utils.py3compat",
294 293 "IPython.utils.sentinel",
295 294 "IPython.utils.strdispatch",
296 295 "IPython.utils.sysinfo",
297 296 "IPython.utils.syspathcontext",
298 297 "IPython.utils.tempdir",
299 298 "IPython.utils.terminal",
300 299 "IPython.utils.timing",
301 300 "IPython.utils.tokenutil",
302 301 "IPython.utils.version",
303 302 "IPython.utils.wildcard",
304 303
305 304 ]
306 305 disallow_untyped_defs = false
307 306 ignore_errors = true
308 307 ignore_missing_imports = true
309 308 disallow_untyped_calls = false
310 309 disallow_incomplete_defs = false
311 310 check_untyped_defs = false
312 311 disallow_untyped_decorators = false
313 312
314 313 [tool.pytest.ini_options]
315 314 addopts = [
316 315 "--durations=10",
317 316 "-pIPython.testing.plugin.pytest_ipdoctest",
318 317 "--ipdoctest-modules",
319 318 "--ignore=docs",
320 319 "--ignore=examples",
321 320 "--ignore=htmlcov",
322 321 "--ignore=ipython_kernel",
323 322 "--ignore=ipython_parallel",
324 323 "--ignore=results",
325 324 "--ignore=tmp",
326 325 "--ignore=tools",
327 326 "--ignore=traitlets",
328 327 "--ignore=IPython/core/tests/daft_extension",
329 328 "--ignore=IPython/sphinxext",
330 329 "--ignore=IPython/terminal/pt_inputhooks",
331 330 "--ignore=IPython/__main__.py",
332 331 "--ignore=IPython/external/qt_for_kernel.py",
333 332 "--ignore=IPython/html/widgets/widget_link.py",
334 333 "--ignore=IPython/html/widgets/widget_output.py",
335 334 "--ignore=IPython/terminal/console.py",
336 335 "--ignore=IPython/utils/_process_cli.py",
337 336 "--ignore=IPython/utils/_process_posix.py",
338 337 "--ignore=IPython/utils/_process_win32.py",
339 338 "--ignore=IPython/utils/_process_win32_controller.py",
340 339 "--ignore=IPython/utils/daemonize.py",
341 340 "--ignore=IPython/utils/eventful.py",
342 341 "--ignore=IPython/kernel",
343 342 "--ignore=IPython/consoleapp.py",
344 343 "--ignore=IPython/lib/kernel.py",
345 344 "--ignore=IPython/utils/jsonutil.py",
346 345 "--ignore=IPython/utils/localinterfaces.py",
347 346 "--ignore=IPython/utils/log.py",
348 347 "--ignore=IPython/utils/signatures.py",
349 348 "--ignore=IPython/utils/version.py"
350 349 ]
351 350 doctest_optionflags = [
352 351 "NORMALIZE_WHITESPACE",
353 352 "ELLIPSIS"
354 353 ]
355 354 ipdoctest_optionflags = [
356 355 "NORMALIZE_WHITESPACE",
357 356 "ELLIPSIS"
358 357 ]
359 358 asyncio_mode = "strict"
360 359
361 360 [tool.pyright]
362 361 pythonPlatform="All"
363 362
364 363 [tool.setuptools]
365 364 zip-safe = false
366 365 platforms = ["Linux", "Mac OSX", "Windows"]
367 366 license-files = ["LICENSE"]
368 367 include-package-data = false
369 368
370 369 [tool.setuptools.packages.find]
371 370 exclude = ["setupext"]
372 371 namespaces = false
373 372
374 373 [tool.setuptools.package-data]
375 374 "IPython" = ["py.typed"]
376 375 "IPython.core" = ["profile/README*"]
377 376 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
378 377 "IPython.lib.tests" = ["*.wav"]
379 378 "IPython.testing.plugin" = ["*.txt"]
380 379
381 380 [tool.setuptools.dynamic]
382 381 version = {attr = "IPython.core.release.__version__"}
383 382
384 383 [tool.coverage.run]
385 384 omit = [
386 385 # omit everything in /tmp as we run tempfile
387 386 "/tmp/*",
388 387 ]
General Comments 0
You need to be logged in to leave comments. Login now