##// END OF EJS Templates
Merge pull request #12383 from cool-RR/2020-06-09-raise-from...
Matthias Bussonnier -
r25834:f8c9ea7d merge
parent child Browse files
Show More
@@ -1,1206 +1,1206 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from binascii import b2a_base64, hexlify
9 9 import json
10 10 import mimetypes
11 11 import os
12 12 import struct
13 13 import warnings
14 14 from copy import deepcopy
15 15 from os.path import splitext
16 16 from pathlib import Path, PurePath
17 17
18 18 from IPython.utils.py3compat import cast_unicode
19 19 from IPython.testing.skipdoctest import skip_doctest
20 20 from . import display_functions
21 21
22 22
23 23 __all__ = ['display_pretty', 'display_html', 'display_markdown',
24 24 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
25 25 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
26 26 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
27 27 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
28 28 'set_matplotlib_close',
29 29 'Video']
30 30
31 31 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
32 32
33 33 __all__ = __all__ + _deprecated_names
34 34
35 35
36 36 # ----- warn to import from IPython.display -----
37 37
38 38 from warnings import warn
39 39
40 40
41 41 def __getattr__(name):
42 42 if name in _deprecated_names:
43 43 warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2)
44 44 return getattr(display_functions, name)
45 45
46 46 if name in globals().keys():
47 47 return globals()[name]
48 48 else:
49 49 raise AttributeError(f"module {__name__} has no attribute {name}")
50 50
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # utility functions
54 54 #-----------------------------------------------------------------------------
55 55
56 56 def _safe_exists(path):
57 57 """Check path, but don't let exceptions raise"""
58 58 try:
59 59 return os.path.exists(path)
60 60 except Exception:
61 61 return False
62 62
63 63
64 64 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
65 65 """internal implementation of all display_foo methods
66 66
67 67 Parameters
68 68 ----------
69 69 mimetype : str
70 70 The mimetype to be published (e.g. 'image/png')
71 71 *objs : object
72 72 The Python objects to display, or if raw=True raw text data to
73 73 display.
74 74 raw : bool
75 75 Are the data objects raw data or Python objects that need to be
76 76 formatted before display? [default: False]
77 77 metadata : dict (optional)
78 78 Metadata to be associated with the specific mimetype output.
79 79 """
80 80 if metadata:
81 81 metadata = {mimetype: metadata}
82 82 if raw:
83 83 # turn list of pngdata into list of { 'image/png': pngdata }
84 84 objs = [ {mimetype: obj} for obj in objs ]
85 85 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
86 86
87 87 #-----------------------------------------------------------------------------
88 88 # Main functions
89 89 #-----------------------------------------------------------------------------
90 90
91 91
92 92 def display_pretty(*objs, **kwargs):
93 93 """Display the pretty (default) representation of an object.
94 94
95 95 Parameters
96 96 ----------
97 97 *objs : object
98 98 The Python objects to display, or if raw=True raw text data to
99 99 display.
100 100 raw : bool
101 101 Are the data objects raw data or Python objects that need to be
102 102 formatted before display? [default: False]
103 103 metadata : dict (optional)
104 104 Metadata to be associated with the specific mimetype output.
105 105 """
106 106 _display_mimetype('text/plain', objs, **kwargs)
107 107
108 108
109 109 def display_html(*objs, **kwargs):
110 110 """Display the HTML representation of an object.
111 111
112 112 Note: If raw=False and the object does not have a HTML
113 113 representation, no HTML will be shown.
114 114
115 115 Parameters
116 116 ----------
117 117 *objs : object
118 118 The Python objects to display, or if raw=True raw HTML data to
119 119 display.
120 120 raw : bool
121 121 Are the data objects raw data or Python objects that need to be
122 122 formatted before display? [default: False]
123 123 metadata : dict (optional)
124 124 Metadata to be associated with the specific mimetype output.
125 125 """
126 126 _display_mimetype('text/html', objs, **kwargs)
127 127
128 128
129 129 def display_markdown(*objs, **kwargs):
130 130 """Displays the Markdown representation of an object.
131 131
132 132 Parameters
133 133 ----------
134 134 *objs : object
135 135 The Python objects to display, or if raw=True raw markdown data to
136 136 display.
137 137 raw : bool
138 138 Are the data objects raw data or Python objects that need to be
139 139 formatted before display? [default: False]
140 140 metadata : dict (optional)
141 141 Metadata to be associated with the specific mimetype output.
142 142 """
143 143
144 144 _display_mimetype('text/markdown', objs, **kwargs)
145 145
146 146
147 147 def display_svg(*objs, **kwargs):
148 148 """Display the SVG representation of an object.
149 149
150 150 Parameters
151 151 ----------
152 152 *objs : object
153 153 The Python objects to display, or if raw=True raw svg data to
154 154 display.
155 155 raw : bool
156 156 Are the data objects raw data or Python objects that need to be
157 157 formatted before display? [default: False]
158 158 metadata : dict (optional)
159 159 Metadata to be associated with the specific mimetype output.
160 160 """
161 161 _display_mimetype('image/svg+xml', objs, **kwargs)
162 162
163 163
164 164 def display_png(*objs, **kwargs):
165 165 """Display the PNG representation of an object.
166 166
167 167 Parameters
168 168 ----------
169 169 *objs : object
170 170 The Python objects to display, or if raw=True raw png data to
171 171 display.
172 172 raw : bool
173 173 Are the data objects raw data or Python objects that need to be
174 174 formatted before display? [default: False]
175 175 metadata : dict (optional)
176 176 Metadata to be associated with the specific mimetype output.
177 177 """
178 178 _display_mimetype('image/png', objs, **kwargs)
179 179
180 180
181 181 def display_jpeg(*objs, **kwargs):
182 182 """Display the JPEG representation of an object.
183 183
184 184 Parameters
185 185 ----------
186 186 *objs : object
187 187 The Python objects to display, or if raw=True raw JPEG data to
188 188 display.
189 189 raw : bool
190 190 Are the data objects raw data or Python objects that need to be
191 191 formatted before display? [default: False]
192 192 metadata : dict (optional)
193 193 Metadata to be associated with the specific mimetype output.
194 194 """
195 195 _display_mimetype('image/jpeg', objs, **kwargs)
196 196
197 197
198 198 def display_latex(*objs, **kwargs):
199 199 """Display the LaTeX representation of an object.
200 200
201 201 Parameters
202 202 ----------
203 203 *objs : object
204 204 The Python objects to display, or if raw=True raw latex data to
205 205 display.
206 206 raw : bool
207 207 Are the data objects raw data or Python objects that need to be
208 208 formatted before display? [default: False]
209 209 metadata : dict (optional)
210 210 Metadata to be associated with the specific mimetype output.
211 211 """
212 212 _display_mimetype('text/latex', objs, **kwargs)
213 213
214 214
215 215 def display_json(*objs, **kwargs):
216 216 """Display the JSON representation of an object.
217 217
218 218 Note that not many frontends support displaying JSON.
219 219
220 220 Parameters
221 221 ----------
222 222 *objs : object
223 223 The Python objects to display, or if raw=True raw json data to
224 224 display.
225 225 raw : bool
226 226 Are the data objects raw data or Python objects that need to be
227 227 formatted before display? [default: False]
228 228 metadata : dict (optional)
229 229 Metadata to be associated with the specific mimetype output.
230 230 """
231 231 _display_mimetype('application/json', objs, **kwargs)
232 232
233 233
234 234 def display_javascript(*objs, **kwargs):
235 235 """Display the Javascript representation of an object.
236 236
237 237 Parameters
238 238 ----------
239 239 *objs : object
240 240 The Python objects to display, or if raw=True raw javascript data to
241 241 display.
242 242 raw : bool
243 243 Are the data objects raw data or Python objects that need to be
244 244 formatted before display? [default: False]
245 245 metadata : dict (optional)
246 246 Metadata to be associated with the specific mimetype output.
247 247 """
248 248 _display_mimetype('application/javascript', objs, **kwargs)
249 249
250 250
251 251 def display_pdf(*objs, **kwargs):
252 252 """Display the PDF representation of an object.
253 253
254 254 Parameters
255 255 ----------
256 256 *objs : object
257 257 The Python objects to display, or if raw=True raw javascript data to
258 258 display.
259 259 raw : bool
260 260 Are the data objects raw data or Python objects that need to be
261 261 formatted before display? [default: False]
262 262 metadata : dict (optional)
263 263 Metadata to be associated with the specific mimetype output.
264 264 """
265 265 _display_mimetype('application/pdf', objs, **kwargs)
266 266
267 267
268 268 #-----------------------------------------------------------------------------
269 269 # Smart classes
270 270 #-----------------------------------------------------------------------------
271 271
272 272
273 273 class DisplayObject(object):
274 274 """An object that wraps data to be displayed."""
275 275
276 276 _read_flags = 'r'
277 277 _show_mem_addr = False
278 278 metadata = None
279 279
280 280 def __init__(self, data=None, url=None, filename=None, metadata=None):
281 281 """Create a display object given raw data.
282 282
283 283 When this object is returned by an expression or passed to the
284 284 display function, it will result in the data being displayed
285 285 in the frontend. The MIME type of the data should match the
286 286 subclasses used, so the Png subclass should be used for 'image/png'
287 287 data. If the data is a URL, the data will first be downloaded
288 288 and then displayed. If
289 289
290 290 Parameters
291 291 ----------
292 292 data : unicode, str or bytes
293 293 The raw data or a URL or file to load the data from
294 294 url : unicode
295 295 A URL to download the data from.
296 296 filename : unicode
297 297 Path to a local file to load the data from.
298 298 metadata : dict
299 299 Dict of metadata associated to be the object when displayed
300 300 """
301 301 if isinstance(data, (Path, PurePath)):
302 302 data = str(data)
303 303
304 304 if data is not None and isinstance(data, str):
305 305 if data.startswith('http') and url is None:
306 306 url = data
307 307 filename = None
308 308 data = None
309 309 elif _safe_exists(data) and filename is None:
310 310 url = None
311 311 filename = data
312 312 data = None
313 313
314 314 self.url = url
315 315 self.filename = filename
316 316 # because of @data.setter methods in
317 317 # subclasses ensure url and filename are set
318 318 # before assigning to self.data
319 319 self.data = data
320 320
321 321 if metadata is not None:
322 322 self.metadata = metadata
323 323 elif self.metadata is None:
324 324 self.metadata = {}
325 325
326 326 self.reload()
327 327 self._check_data()
328 328
329 329 def __repr__(self):
330 330 if not self._show_mem_addr:
331 331 cls = self.__class__
332 332 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
333 333 else:
334 334 r = super(DisplayObject, self).__repr__()
335 335 return r
336 336
337 337 def _check_data(self):
338 338 """Override in subclasses if there's something to check."""
339 339 pass
340 340
341 341 def _data_and_metadata(self):
342 342 """shortcut for returning metadata with shape information, if defined"""
343 343 if self.metadata:
344 344 return self.data, deepcopy(self.metadata)
345 345 else:
346 346 return self.data
347 347
348 348 def reload(self):
349 349 """Reload the raw data from file or URL."""
350 350 if self.filename is not None:
351 351 with open(self.filename, self._read_flags) as f:
352 352 self.data = f.read()
353 353 elif self.url is not None:
354 354 # Deferred import
355 355 from urllib.request import urlopen
356 356 response = urlopen(self.url)
357 357 data = response.read()
358 358 # extract encoding from header, if there is one:
359 359 encoding = None
360 360 if 'content-type' in response.headers:
361 361 for sub in response.headers['content-type'].split(';'):
362 362 sub = sub.strip()
363 363 if sub.startswith('charset'):
364 364 encoding = sub.split('=')[-1].strip()
365 365 break
366 366 if 'content-encoding' in response.headers:
367 367 # TODO: do deflate?
368 368 if 'gzip' in response.headers['content-encoding']:
369 369 import gzip
370 370 from io import BytesIO
371 371 with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp:
372 372 encoding = None
373 373 data = fp.read()
374 374
375 375 # decode data, if an encoding was specified
376 376 # We only touch self.data once since
377 377 # subclasses such as SVG have @data.setter methods
378 378 # that transform self.data into ... well svg.
379 379 if encoding:
380 380 self.data = data.decode(encoding, 'replace')
381 381 else:
382 382 self.data = data
383 383
384 384
385 385 class TextDisplayObject(DisplayObject):
386 386 """Validate that display data is text"""
387 387 def _check_data(self):
388 388 if self.data is not None and not isinstance(self.data, str):
389 389 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
390 390
391 391 class Pretty(TextDisplayObject):
392 392
393 393 def _repr_pretty_(self, pp, cycle):
394 394 return pp.text(self.data)
395 395
396 396
397 397 class HTML(TextDisplayObject):
398 398
399 399 def __init__(self, data=None, url=None, filename=None, metadata=None):
400 400 def warn():
401 401 if not data:
402 402 return False
403 403
404 404 #
405 405 # Avoid calling lower() on the entire data, because it could be a
406 406 # long string and we're only interested in its beginning and end.
407 407 #
408 408 prefix = data[:10].lower()
409 409 suffix = data[-10:].lower()
410 410 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
411 411
412 412 if warn():
413 413 warnings.warn("Consider using IPython.display.IFrame instead")
414 414 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
415 415
416 416 def _repr_html_(self):
417 417 return self._data_and_metadata()
418 418
419 419 def __html__(self):
420 420 """
421 421 This method exists to inform other HTML-using modules (e.g. Markupsafe,
422 422 htmltag, etc) that this object is HTML and does not need things like
423 423 special characters (<>&) escaped.
424 424 """
425 425 return self._repr_html_()
426 426
427 427
428 428 class Markdown(TextDisplayObject):
429 429
430 430 def _repr_markdown_(self):
431 431 return self._data_and_metadata()
432 432
433 433
434 434 class Math(TextDisplayObject):
435 435
436 436 def _repr_latex_(self):
437 437 s = r"$\displaystyle %s$" % self.data.strip('$')
438 438 if self.metadata:
439 439 return s, deepcopy(self.metadata)
440 440 else:
441 441 return s
442 442
443 443
444 444 class Latex(TextDisplayObject):
445 445
446 446 def _repr_latex_(self):
447 447 return self._data_and_metadata()
448 448
449 449
450 450 class SVG(DisplayObject):
451 451 """Embed an SVG into the display.
452 452
453 453 Note if you just want to view a svg image via a URL use `:class:Image` with
454 454 a url=URL keyword argument.
455 455 """
456 456
457 457 _read_flags = 'rb'
458 458 # wrap data in a property, which extracts the <svg> tag, discarding
459 459 # document headers
460 460 _data = None
461 461
462 462 @property
463 463 def data(self):
464 464 return self._data
465 465
466 466 @data.setter
467 467 def data(self, svg):
468 468 if svg is None:
469 469 self._data = None
470 470 return
471 471 # parse into dom object
472 472 from xml.dom import minidom
473 473 x = minidom.parseString(svg)
474 474 # get svg tag (should be 1)
475 475 found_svg = x.getElementsByTagName('svg')
476 476 if found_svg:
477 477 svg = found_svg[0].toxml()
478 478 else:
479 479 # fallback on the input, trust the user
480 480 # but this is probably an error.
481 481 pass
482 482 svg = cast_unicode(svg)
483 483 self._data = svg
484 484
485 485 def _repr_svg_(self):
486 486 return self._data_and_metadata()
487 487
488 488 class ProgressBar(DisplayObject):
489 489 """Progressbar supports displaying a progressbar like element
490 490 """
491 491 def __init__(self, total):
492 492 """Creates a new progressbar
493 493
494 494 Parameters
495 495 ----------
496 496 total : int
497 497 maximum size of the progressbar
498 498 """
499 499 self.total = total
500 500 self._progress = 0
501 501 self.html_width = '60ex'
502 502 self.text_width = 60
503 503 self._display_id = hexlify(os.urandom(8)).decode('ascii')
504 504
505 505 def __repr__(self):
506 506 fraction = self.progress / self.total
507 507 filled = '=' * int(fraction * self.text_width)
508 508 rest = ' ' * (self.text_width - len(filled))
509 509 return '[{}{}] {}/{}'.format(
510 510 filled, rest,
511 511 self.progress, self.total,
512 512 )
513 513
514 514 def _repr_html_(self):
515 515 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
516 516 self.html_width, self.total, self.progress)
517 517
518 518 def display(self):
519 519 display(self, display_id=self._display_id)
520 520
521 521 def update(self):
522 522 display(self, display_id=self._display_id, update=True)
523 523
524 524 @property
525 525 def progress(self):
526 526 return self._progress
527 527
528 528 @progress.setter
529 529 def progress(self, value):
530 530 self._progress = value
531 531 self.update()
532 532
533 533 def __iter__(self):
534 534 self.display()
535 535 self._progress = -1 # First iteration is 0
536 536 return self
537 537
538 538 def __next__(self):
539 539 """Returns current value and increments display by one."""
540 540 self.progress += 1
541 541 if self.progress < self.total:
542 542 return self.progress
543 543 else:
544 544 raise StopIteration()
545 545
546 546 class JSON(DisplayObject):
547 547 """JSON expects a JSON-able dict or list
548 548
549 549 not an already-serialized JSON string.
550 550
551 551 Scalar types (None, number, string) are not allowed, only dict or list containers.
552 552 """
553 553 # wrap data in a property, which warns about passing already-serialized JSON
554 554 _data = None
555 555 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
556 556 """Create a JSON display object given raw data.
557 557
558 558 Parameters
559 559 ----------
560 560 data : dict or list
561 561 JSON data to display. Not an already-serialized JSON string.
562 562 Scalar types (None, number, string) are not allowed, only dict
563 563 or list containers.
564 564 url : unicode
565 565 A URL to download the data from.
566 566 filename : unicode
567 567 Path to a local file to load the data from.
568 568 expanded : boolean
569 569 Metadata to control whether a JSON display component is expanded.
570 570 metadata: dict
571 571 Specify extra metadata to attach to the json display object.
572 572 root : str
573 573 The name of the root element of the JSON tree
574 574 """
575 575 self.metadata = {
576 576 'expanded': expanded,
577 577 'root': root,
578 578 }
579 579 if metadata:
580 580 self.metadata.update(metadata)
581 581 if kwargs:
582 582 self.metadata.update(kwargs)
583 583 super(JSON, self).__init__(data=data, url=url, filename=filename)
584 584
585 585 def _check_data(self):
586 586 if self.data is not None and not isinstance(self.data, (dict, list)):
587 587 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
588 588
589 589 @property
590 590 def data(self):
591 591 return self._data
592 592
593 593 @data.setter
594 594 def data(self, data):
595 595 if isinstance(data, (Path, PurePath)):
596 596 data = str(data)
597 597
598 598 if isinstance(data, str):
599 599 if self.filename is None and self.url is None:
600 600 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
601 601 data = json.loads(data)
602 602 self._data = data
603 603
604 604 def _data_and_metadata(self):
605 605 return self.data, self.metadata
606 606
607 607 def _repr_json_(self):
608 608 return self._data_and_metadata()
609 609
610 610 _css_t = """var link = document.createElement("link");
611 611 link.ref = "stylesheet";
612 612 link.type = "text/css";
613 613 link.href = "%s";
614 614 document.head.appendChild(link);
615 615 """
616 616
617 617 _lib_t1 = """new Promise(function(resolve, reject) {
618 618 var script = document.createElement("script");
619 619 script.onload = resolve;
620 620 script.onerror = reject;
621 621 script.src = "%s";
622 622 document.head.appendChild(script);
623 623 }).then(() => {
624 624 """
625 625
626 626 _lib_t2 = """
627 627 });"""
628 628
629 629 class GeoJSON(JSON):
630 630 """GeoJSON expects JSON-able dict
631 631
632 632 not an already-serialized JSON string.
633 633
634 634 Scalar types (None, number, string) are not allowed, only dict containers.
635 635 """
636 636
637 637 def __init__(self, *args, **kwargs):
638 638 """Create a GeoJSON display object given raw data.
639 639
640 640 Parameters
641 641 ----------
642 642 data : dict or list
643 643 VegaLite data. Not an already-serialized JSON string.
644 644 Scalar types (None, number, string) are not allowed, only dict
645 645 or list containers.
646 646 url_template : string
647 647 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
648 648 layer_options : dict
649 649 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
650 650 url : unicode
651 651 A URL to download the data from.
652 652 filename : unicode
653 653 Path to a local file to load the data from.
654 654 metadata: dict
655 655 Specify extra metadata to attach to the json display object.
656 656
657 657 Examples
658 658 --------
659 659
660 660 The following will display an interactive map of Mars with a point of
661 661 interest on frontend that do support GeoJSON display.
662 662
663 663 >>> from IPython.display import GeoJSON
664 664
665 665 >>> GeoJSON(data={
666 666 ... "type": "Feature",
667 667 ... "geometry": {
668 668 ... "type": "Point",
669 669 ... "coordinates": [-81.327, 296.038]
670 670 ... }
671 671 ... },
672 672 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
673 673 ... layer_options={
674 674 ... "basemap_id": "celestia_mars-shaded-16k_global",
675 675 ... "attribution" : "Celestia/praesepe",
676 676 ... "minZoom" : 0,
677 677 ... "maxZoom" : 18,
678 678 ... })
679 679 <IPython.core.display.GeoJSON object>
680 680
681 681 In the terminal IPython, you will only see the text representation of
682 682 the GeoJSON object.
683 683
684 684 """
685 685
686 686 super(GeoJSON, self).__init__(*args, **kwargs)
687 687
688 688
689 689 def _ipython_display_(self):
690 690 bundle = {
691 691 'application/geo+json': self.data,
692 692 'text/plain': '<IPython.display.GeoJSON object>'
693 693 }
694 694 metadata = {
695 695 'application/geo+json': self.metadata
696 696 }
697 697 display(bundle, metadata=metadata, raw=True)
698 698
699 699 class Javascript(TextDisplayObject):
700 700
701 701 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
702 702 """Create a Javascript display object given raw data.
703 703
704 704 When this object is returned by an expression or passed to the
705 705 display function, it will result in the data being displayed
706 706 in the frontend. If the data is a URL, the data will first be
707 707 downloaded and then displayed.
708 708
709 709 In the Notebook, the containing element will be available as `element`,
710 710 and jQuery will be available. Content appended to `element` will be
711 711 visible in the output area.
712 712
713 713 Parameters
714 714 ----------
715 715 data : unicode, str or bytes
716 716 The Javascript source code or a URL to download it from.
717 717 url : unicode
718 718 A URL to download the data from.
719 719 filename : unicode
720 720 Path to a local file to load the data from.
721 721 lib : list or str
722 722 A sequence of Javascript library URLs to load asynchronously before
723 723 running the source code. The full URLs of the libraries should
724 724 be given. A single Javascript library URL can also be given as a
725 725 string.
726 726 css: : list or str
727 727 A sequence of css files to load before running the source code.
728 728 The full URLs of the css files should be given. A single css URL
729 729 can also be given as a string.
730 730 """
731 731 if isinstance(lib, str):
732 732 lib = [lib]
733 733 elif lib is None:
734 734 lib = []
735 735 if isinstance(css, str):
736 736 css = [css]
737 737 elif css is None:
738 738 css = []
739 739 if not isinstance(lib, (list,tuple)):
740 740 raise TypeError('expected sequence, got: %r' % lib)
741 741 if not isinstance(css, (list,tuple)):
742 742 raise TypeError('expected sequence, got: %r' % css)
743 743 self.lib = lib
744 744 self.css = css
745 745 super(Javascript, self).__init__(data=data, url=url, filename=filename)
746 746
747 747 def _repr_javascript_(self):
748 748 r = ''
749 749 for c in self.css:
750 750 r += _css_t % c
751 751 for l in self.lib:
752 752 r += _lib_t1 % l
753 753 r += self.data
754 754 r += _lib_t2*len(self.lib)
755 755 return r
756 756
757 757 # constants for identifying png/jpeg data
758 758 _PNG = b'\x89PNG\r\n\x1a\n'
759 759 _JPEG = b'\xff\xd8'
760 760
761 761 def _pngxy(data):
762 762 """read the (width, height) from a PNG header"""
763 763 ihdr = data.index(b'IHDR')
764 764 # next 8 bytes are width/height
765 765 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
766 766
767 767 def _jpegxy(data):
768 768 """read the (width, height) from a JPEG header"""
769 769 # adapted from http://www.64lines.com/jpeg-width-height
770 770
771 771 idx = 4
772 772 while True:
773 773 block_size = struct.unpack('>H', data[idx:idx+2])[0]
774 774 idx = idx + block_size
775 775 if data[idx:idx+2] == b'\xFF\xC0':
776 776 # found Start of Frame
777 777 iSOF = idx
778 778 break
779 779 else:
780 780 # read another block
781 781 idx += 2
782 782
783 783 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
784 784 return w, h
785 785
786 786 def _gifxy(data):
787 787 """read the (width, height) from a GIF header"""
788 788 return struct.unpack('<HH', data[6:10])
789 789
790 790
791 791 class Image(DisplayObject):
792 792
793 793 _read_flags = 'rb'
794 794 _FMT_JPEG = u'jpeg'
795 795 _FMT_PNG = u'png'
796 796 _FMT_GIF = u'gif'
797 797 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
798 798 _MIMETYPES = {
799 799 _FMT_PNG: 'image/png',
800 800 _FMT_JPEG: 'image/jpeg',
801 801 _FMT_GIF: 'image/gif',
802 802 }
803 803
804 804 def __init__(self, data=None, url=None, filename=None, format=None,
805 805 embed=None, width=None, height=None, retina=False,
806 806 unconfined=False, metadata=None):
807 807 """Create a PNG/JPEG/GIF image object given raw data.
808 808
809 809 When this object is returned by an input cell or passed to the
810 810 display function, it will result in the image being displayed
811 811 in the frontend.
812 812
813 813 Parameters
814 814 ----------
815 815 data : unicode, str or bytes
816 816 The raw image data or a URL or filename to load the data from.
817 817 This always results in embedded image data.
818 818 url : unicode
819 819 A URL to download the data from. If you specify `url=`,
820 820 the image data will not be embedded unless you also specify `embed=True`.
821 821 filename : unicode
822 822 Path to a local file to load the data from.
823 823 Images from a file are always embedded.
824 824 format : unicode
825 825 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
826 826 for format will be inferred from the filename extension.
827 827 embed : bool
828 828 Should the image data be embedded using a data URI (True) or be
829 829 loaded using an <img> tag. Set this to True if you want the image
830 830 to be viewable later with no internet connection in the notebook.
831 831
832 832 Default is `True`, unless the keyword argument `url` is set, then
833 833 default value is `False`.
834 834
835 835 Note that QtConsole is not able to display images if `embed` is set to `False`
836 836 width : int
837 837 Width in pixels to which to constrain the image in html
838 838 height : int
839 839 Height in pixels to which to constrain the image in html
840 840 retina : bool
841 841 Automatically set the width and height to half of the measured
842 842 width and height.
843 843 This only works for embedded images because it reads the width/height
844 844 from image data.
845 845 For non-embedded images, you can just set the desired display width
846 846 and height directly.
847 847 unconfined: bool
848 848 Set unconfined=True to disable max-width confinement of the image.
849 849 metadata: dict
850 850 Specify extra metadata to attach to the image.
851 851
852 852 Examples
853 853 --------
854 854 # embedded image data, works in qtconsole and notebook
855 855 # when passed positionally, the first arg can be any of raw image data,
856 856 # a URL, or a filename from which to load image data.
857 857 # The result is always embedding image data for inline images.
858 858 Image('http://www.google.fr/images/srpr/logo3w.png')
859 859 Image('/path/to/image.jpg')
860 860 Image(b'RAW_PNG_DATA...')
861 861
862 862 # Specifying Image(url=...) does not embed the image data,
863 863 # it only generates `<img>` tag with a link to the source.
864 864 # This will not work in the qtconsole or offline.
865 865 Image(url='http://www.google.fr/images/srpr/logo3w.png')
866 866
867 867 """
868 868 if isinstance(data, (Path, PurePath)):
869 869 data = str(data)
870 870
871 871 if filename is not None:
872 872 ext = self._find_ext(filename)
873 873 elif url is not None:
874 874 ext = self._find_ext(url)
875 875 elif data is None:
876 876 raise ValueError("No image data found. Expecting filename, url, or data.")
877 877 elif isinstance(data, str) and (
878 878 data.startswith('http') or _safe_exists(data)
879 879 ):
880 880 ext = self._find_ext(data)
881 881 else:
882 882 ext = None
883 883
884 884 if format is None:
885 885 if ext is not None:
886 886 if ext == u'jpg' or ext == u'jpeg':
887 887 format = self._FMT_JPEG
888 888 elif ext == u'png':
889 889 format = self._FMT_PNG
890 890 elif ext == u'gif':
891 891 format = self._FMT_GIF
892 892 else:
893 893 format = ext.lower()
894 894 elif isinstance(data, bytes):
895 895 # infer image type from image data header,
896 896 # only if format has not been specified.
897 897 if data[:2] == _JPEG:
898 898 format = self._FMT_JPEG
899 899
900 900 # failed to detect format, default png
901 901 if format is None:
902 902 format = self._FMT_PNG
903 903
904 904 if format.lower() == 'jpg':
905 905 # jpg->jpeg
906 906 format = self._FMT_JPEG
907 907
908 908 self.format = format.lower()
909 909 self.embed = embed if embed is not None else (url is None)
910 910
911 911 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
912 912 raise ValueError("Cannot embed the '%s' image format" % (self.format))
913 913 if self.embed:
914 914 self._mimetype = self._MIMETYPES.get(self.format)
915 915
916 916 self.width = width
917 917 self.height = height
918 918 self.retina = retina
919 919 self.unconfined = unconfined
920 920 super(Image, self).__init__(data=data, url=url, filename=filename,
921 921 metadata=metadata)
922 922
923 923 if self.width is None and self.metadata.get('width', {}):
924 924 self.width = metadata['width']
925 925
926 926 if self.height is None and self.metadata.get('height', {}):
927 927 self.height = metadata['height']
928 928
929 929 if retina:
930 930 self._retina_shape()
931 931
932 932
933 933 def _retina_shape(self):
934 934 """load pixel-doubled width and height from image data"""
935 935 if not self.embed:
936 936 return
937 937 if self.format == self._FMT_PNG:
938 938 w, h = _pngxy(self.data)
939 939 elif self.format == self._FMT_JPEG:
940 940 w, h = _jpegxy(self.data)
941 941 elif self.format == self._FMT_GIF:
942 942 w, h = _gifxy(self.data)
943 943 else:
944 944 # retina only supports png
945 945 return
946 946 self.width = w // 2
947 947 self.height = h // 2
948 948
949 949 def reload(self):
950 950 """Reload the raw data from file or URL."""
951 951 if self.embed:
952 952 super(Image,self).reload()
953 953 if self.retina:
954 954 self._retina_shape()
955 955
956 956 def _repr_html_(self):
957 957 if not self.embed:
958 958 width = height = klass = ''
959 959 if self.width:
960 960 width = ' width="%d"' % self.width
961 961 if self.height:
962 962 height = ' height="%d"' % self.height
963 963 if self.unconfined:
964 964 klass = ' class="unconfined"'
965 965 return u'<img src="{url}"{width}{height}{klass}/>'.format(
966 966 url=self.url,
967 967 width=width,
968 968 height=height,
969 969 klass=klass,
970 970 )
971 971
972 972 def _repr_mimebundle_(self, include=None, exclude=None):
973 973 """Return the image as a mimebundle
974 974
975 975 Any new mimetype support should be implemented here.
976 976 """
977 977 if self.embed:
978 978 mimetype = self._mimetype
979 979 data, metadata = self._data_and_metadata(always_both=True)
980 980 if metadata:
981 981 metadata = {mimetype: metadata}
982 982 return {mimetype: data}, metadata
983 983 else:
984 984 return {'text/html': self._repr_html_()}
985 985
986 986 def _data_and_metadata(self, always_both=False):
987 987 """shortcut for returning metadata with shape information, if defined"""
988 988 try:
989 989 b64_data = b2a_base64(self.data).decode('ascii')
990 except TypeError:
990 except TypeError as e:
991 991 raise FileNotFoundError(
992 "No such file or directory: '%s'" % (self.data))
992 "No such file or directory: '%s'" % (self.data)) from e
993 993 md = {}
994 994 if self.metadata:
995 995 md.update(self.metadata)
996 996 if self.width:
997 997 md['width'] = self.width
998 998 if self.height:
999 999 md['height'] = self.height
1000 1000 if self.unconfined:
1001 1001 md['unconfined'] = self.unconfined
1002 1002 if md or always_both:
1003 1003 return b64_data, md
1004 1004 else:
1005 1005 return b64_data
1006 1006
1007 1007 def _repr_png_(self):
1008 1008 if self.embed and self.format == self._FMT_PNG:
1009 1009 return self._data_and_metadata()
1010 1010
1011 1011 def _repr_jpeg_(self):
1012 1012 if self.embed and self.format == self._FMT_JPEG:
1013 1013 return self._data_and_metadata()
1014 1014
1015 1015 def _find_ext(self, s):
1016 1016 base, ext = splitext(s)
1017 1017
1018 1018 if not ext:
1019 1019 return base
1020 1020
1021 1021 # `splitext` includes leading period, so we skip it
1022 1022 return ext[1:].lower()
1023 1023
1024 1024
1025 1025 class Video(DisplayObject):
1026 1026
1027 1027 def __init__(self, data=None, url=None, filename=None, embed=False,
1028 1028 mimetype=None, width=None, height=None, html_attributes="controls"):
1029 1029 """Create a video object given raw data or an URL.
1030 1030
1031 1031 When this object is returned by an input cell or passed to the
1032 1032 display function, it will result in the video being displayed
1033 1033 in the frontend.
1034 1034
1035 1035 Parameters
1036 1036 ----------
1037 1037 data : unicode, str or bytes
1038 1038 The raw video data or a URL or filename to load the data from.
1039 1039 Raw data will require passing `embed=True`.
1040 1040 url : unicode
1041 1041 A URL for the video. If you specify `url=`,
1042 1042 the image data will not be embedded.
1043 1043 filename : unicode
1044 1044 Path to a local file containing the video.
1045 1045 Will be interpreted as a local URL unless `embed=True`.
1046 1046 embed : bool
1047 1047 Should the video be embedded using a data URI (True) or be
1048 1048 loaded using a <video> tag (False).
1049 1049
1050 1050 Since videos are large, embedding them should be avoided, if possible.
1051 1051 You must confirm embedding as your intention by passing `embed=True`.
1052 1052
1053 1053 Local files can be displayed with URLs without embedding the content, via::
1054 1054
1055 1055 Video('./video.mp4')
1056 1056
1057 1057 mimetype: unicode
1058 1058 Specify the mimetype for embedded videos.
1059 1059 Default will be guessed from file extension, if available.
1060 1060 width : int
1061 1061 Width in pixels to which to constrain the video in HTML.
1062 1062 If not supplied, defaults to the width of the video.
1063 1063 height : int
1064 1064 Height in pixels to which to constrain the video in html.
1065 1065 If not supplied, defaults to the height of the video.
1066 1066 html_attributes : str
1067 1067 Attributes for the HTML `<video>` block.
1068 1068 Default: `"controls"` to get video controls.
1069 1069 Other examples: `"controls muted"` for muted video with controls,
1070 1070 `"loop autoplay"` for looping autoplaying video without controls.
1071 1071
1072 1072 Examples
1073 1073 --------
1074 1074
1075 1075 ::
1076 1076
1077 1077 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1078 1078 Video('path/to/video.mp4')
1079 1079 Video('path/to/video.mp4', embed=True)
1080 1080 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1081 1081 Video(b'raw-videodata', embed=True)
1082 1082 """
1083 1083 if isinstance(data, (Path, PurePath)):
1084 1084 data = str(data)
1085 1085
1086 1086 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1087 1087 url = data
1088 1088 data = None
1089 1089 elif os.path.exists(data):
1090 1090 filename = data
1091 1091 data = None
1092 1092
1093 1093 if data and not embed:
1094 1094 msg = ''.join([
1095 1095 "To embed videos, you must pass embed=True ",
1096 1096 "(this may make your notebook files huge)\n",
1097 1097 "Consider passing Video(url='...')",
1098 1098 ])
1099 1099 raise ValueError(msg)
1100 1100
1101 1101 self.mimetype = mimetype
1102 1102 self.embed = embed
1103 1103 self.width = width
1104 1104 self.height = height
1105 1105 self.html_attributes = html_attributes
1106 1106 super(Video, self).__init__(data=data, url=url, filename=filename)
1107 1107
1108 1108 def _repr_html_(self):
1109 1109 width = height = ''
1110 1110 if self.width:
1111 1111 width = ' width="%d"' % self.width
1112 1112 if self.height:
1113 1113 height = ' height="%d"' % self.height
1114 1114
1115 1115 # External URLs and potentially local files are not embedded into the
1116 1116 # notebook output.
1117 1117 if not self.embed:
1118 1118 url = self.url if self.url is not None else self.filename
1119 1119 output = """<video src="{0}" {1} {2} {3}>
1120 1120 Your browser does not support the <code>video</code> element.
1121 1121 </video>""".format(url, self.html_attributes, width, height)
1122 1122 return output
1123 1123
1124 1124 # Embedded videos are base64-encoded.
1125 1125 mimetype = self.mimetype
1126 1126 if self.filename is not None:
1127 1127 if not mimetype:
1128 1128 mimetype, _ = mimetypes.guess_type(self.filename)
1129 1129
1130 1130 with open(self.filename, 'rb') as f:
1131 1131 video = f.read()
1132 1132 else:
1133 1133 video = self.data
1134 1134 if isinstance(video, str):
1135 1135 # unicode input is already b64-encoded
1136 1136 b64_video = video
1137 1137 else:
1138 1138 b64_video = b2a_base64(video).decode('ascii').rstrip()
1139 1139
1140 1140 output = """<video {0} {1} {2}>
1141 1141 <source src="data:{3};base64,{4}" type="{3}">
1142 1142 Your browser does not support the video tag.
1143 1143 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1144 1144 return output
1145 1145
1146 1146 def reload(self):
1147 1147 # TODO
1148 1148 pass
1149 1149
1150 1150
1151 1151 @skip_doctest
1152 1152 def set_matplotlib_formats(*formats, **kwargs):
1153 1153 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1154 1154
1155 1155 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1156 1156
1157 1157 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1158 1158
1159 1159 To set this in your config files use the following::
1160 1160
1161 1161 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1162 1162 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1163 1163
1164 1164 Parameters
1165 1165 ----------
1166 1166 *formats : strs
1167 1167 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1168 1168 **kwargs :
1169 1169 Keyword args will be relayed to ``figure.canvas.print_figure``.
1170 1170 """
1171 1171 from IPython.core.interactiveshell import InteractiveShell
1172 1172 from IPython.core.pylabtools import select_figure_formats
1173 1173 # build kwargs, starting with InlineBackend config
1174 1174 kw = {}
1175 1175 from ipykernel.pylab.config import InlineBackend
1176 1176 cfg = InlineBackend.instance()
1177 1177 kw.update(cfg.print_figure_kwargs)
1178 1178 kw.update(**kwargs)
1179 1179 shell = InteractiveShell.instance()
1180 1180 select_figure_formats(shell, formats, **kw)
1181 1181
1182 1182 @skip_doctest
1183 1183 def set_matplotlib_close(close=True):
1184 1184 """Set whether the inline backend closes all figures automatically or not.
1185 1185
1186 1186 By default, the inline backend used in the IPython Notebook will close all
1187 1187 matplotlib figures automatically after each cell is run. This means that
1188 1188 plots in different cells won't interfere. Sometimes, you may want to make
1189 1189 a plot in one cell and then refine it in later cells. This can be accomplished
1190 1190 by::
1191 1191
1192 1192 In [1]: set_matplotlib_close(False)
1193 1193
1194 1194 To set this in your config files use the following::
1195 1195
1196 1196 c.InlineBackend.close_figures = False
1197 1197
1198 1198 Parameters
1199 1199 ----------
1200 1200 close : bool
1201 1201 Should all matplotlib figures be automatically closed after each cell is
1202 1202 run?
1203 1203 """
1204 1204 from ipykernel.pylab.config import InlineBackend
1205 1205 cfg = InlineBackend.instance()
1206 1206 cfg.close_figures = close
@@ -1,343 +1,343 b''
1 1 # encoding: utf-8
2 2 """
3 3 Paging capabilities for IPython.core
4 4
5 5 Notes
6 6 -----
7 7
8 8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
9 9 rid of that dependency, we could move it there.
10 10 -----
11 11 """
12 12
13 13 # Copyright (c) IPython Development Team.
14 14 # Distributed under the terms of the Modified BSD License.
15 15
16 16
17 17 import os
18 18 import io
19 19 import re
20 20 import sys
21 21 import tempfile
22 22 import subprocess
23 23
24 24 from io import UnsupportedOperation
25 25
26 26 from IPython import get_ipython
27 27 from IPython.display import display
28 28 from IPython.core.error import TryNext
29 29 from IPython.utils.data import chop
30 30 from IPython.utils.process import system
31 31 from IPython.utils.terminal import get_terminal_size
32 32 from IPython.utils import py3compat
33 33
34 34
35 35 def display_page(strng, start=0, screen_lines=25):
36 36 """Just display, no paging. screen_lines is ignored."""
37 37 if isinstance(strng, dict):
38 38 data = strng
39 39 else:
40 40 if start:
41 41 strng = u'\n'.join(strng.splitlines()[start:])
42 42 data = { 'text/plain': strng }
43 43 display(data, raw=True)
44 44
45 45
46 46 def as_hook(page_func):
47 47 """Wrap a pager func to strip the `self` arg
48 48
49 49 so it can be called as a hook.
50 50 """
51 51 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
52 52
53 53
54 54 esc_re = re.compile(r"(\x1b[^m]+m)")
55 55
56 56 def page_dumb(strng, start=0, screen_lines=25):
57 57 """Very dumb 'pager' in Python, for when nothing else works.
58 58
59 59 Only moves forward, same interface as page(), except for pager_cmd and
60 60 mode.
61 61 """
62 62 if isinstance(strng, dict):
63 63 strng = strng.get('text/plain', '')
64 64 out_ln = strng.splitlines()[start:]
65 65 screens = chop(out_ln,screen_lines-1)
66 66 if len(screens) == 1:
67 67 print(os.linesep.join(screens[0]))
68 68 else:
69 69 last_escape = ""
70 70 for scr in screens[0:-1]:
71 71 hunk = os.linesep.join(scr)
72 72 print(last_escape + hunk)
73 73 if not page_more():
74 74 return
75 75 esc_list = esc_re.findall(hunk)
76 76 if len(esc_list) > 0:
77 77 last_escape = esc_list[-1]
78 78 print(last_escape + os.linesep.join(screens[-1]))
79 79
80 80 def _detect_screen_size(screen_lines_def):
81 81 """Attempt to work out the number of lines on the screen.
82 82
83 83 This is called by page(). It can raise an error (e.g. when run in the
84 84 test suite), so it's separated out so it can easily be called in a try block.
85 85 """
86 86 TERM = os.environ.get('TERM',None)
87 87 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
88 88 # curses causes problems on many terminals other than xterm, and
89 89 # some termios calls lock up on Sun OS5.
90 90 return screen_lines_def
91 91
92 92 try:
93 93 import termios
94 94 import curses
95 95 except ImportError:
96 96 return screen_lines_def
97 97
98 98 # There is a bug in curses, where *sometimes* it fails to properly
99 99 # initialize, and then after the endwin() call is made, the
100 100 # terminal is left in an unusable state. Rather than trying to
101 101 # check every time for this (by requesting and comparing termios
102 102 # flags each time), we just save the initial terminal state and
103 103 # unconditionally reset it every time. It's cheaper than making
104 104 # the checks.
105 105 try:
106 106 term_flags = termios.tcgetattr(sys.stdout)
107 107 except termios.error as err:
108 108 # can fail on Linux 2.6, pager_page will catch the TypeError
109 raise TypeError('termios error: {0}'.format(err))
109 raise TypeError('termios error: {0}'.format(err)) from err
110 110
111 111 try:
112 112 scr = curses.initscr()
113 113 except AttributeError:
114 114 # Curses on Solaris may not be complete, so we can't use it there
115 115 return screen_lines_def
116 116
117 117 screen_lines_real,screen_cols = scr.getmaxyx()
118 118 curses.endwin()
119 119
120 120 # Restore terminal state in case endwin() didn't.
121 121 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
122 122 # Now we have what we needed: the screen size in rows/columns
123 123 return screen_lines_real
124 124 #print '***Screen size:',screen_lines_real,'lines x',\
125 125 #screen_cols,'columns.' # dbg
126 126
127 127 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
128 128 """Display a string, piping through a pager after a certain length.
129 129
130 130 strng can be a mime-bundle dict, supplying multiple representations,
131 131 keyed by mime-type.
132 132
133 133 The screen_lines parameter specifies the number of *usable* lines of your
134 134 terminal screen (total lines minus lines you need to reserve to show other
135 135 information).
136 136
137 137 If you set screen_lines to a number <=0, page() will try to auto-determine
138 138 your screen size and will only use up to (screen_size+screen_lines) for
139 139 printing, paging after that. That is, if you want auto-detection but need
140 140 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
141 141 auto-detection without any lines reserved simply use screen_lines = 0.
142 142
143 143 If a string won't fit in the allowed lines, it is sent through the
144 144 specified pager command. If none given, look for PAGER in the environment,
145 145 and ultimately default to less.
146 146
147 147 If no system pager works, the string is sent through a 'dumb pager'
148 148 written in python, very simplistic.
149 149 """
150 150
151 151 # for compatibility with mime-bundle form:
152 152 if isinstance(strng, dict):
153 153 strng = strng['text/plain']
154 154
155 155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
156 156 TERM = os.environ.get('TERM','dumb')
157 157 if TERM in ['dumb','emacs'] and os.name != 'nt':
158 158 print(strng)
159 159 return
160 160 # chop off the topmost part of the string we don't want to see
161 161 str_lines = strng.splitlines()[start:]
162 162 str_toprint = os.linesep.join(str_lines)
163 163 num_newlines = len(str_lines)
164 164 len_str = len(str_toprint)
165 165
166 166 # Dumb heuristics to guesstimate number of on-screen lines the string
167 167 # takes. Very basic, but good enough for docstrings in reasonable
168 168 # terminals. If someone later feels like refining it, it's not hard.
169 169 numlines = max(num_newlines,int(len_str/80)+1)
170 170
171 171 screen_lines_def = get_terminal_size()[1]
172 172
173 173 # auto-determine screen size
174 174 if screen_lines <= 0:
175 175 try:
176 176 screen_lines += _detect_screen_size(screen_lines_def)
177 177 except (TypeError, UnsupportedOperation):
178 178 print(str_toprint)
179 179 return
180 180
181 181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
182 182 if numlines <= screen_lines :
183 183 #print '*** normal print' # dbg
184 184 print(str_toprint)
185 185 else:
186 186 # Try to open pager and default to internal one if that fails.
187 187 # All failure modes are tagged as 'retval=1', to match the return
188 188 # value of a failed system command. If any intermediate attempt
189 189 # sets retval to 1, at the end we resort to our own page_dumb() pager.
190 190 pager_cmd = get_pager_cmd(pager_cmd)
191 191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
192 192 if os.name == 'nt':
193 193 if pager_cmd.startswith('type'):
194 194 # The default WinXP 'type' command is failing on complex strings.
195 195 retval = 1
196 196 else:
197 197 fd, tmpname = tempfile.mkstemp('.txt')
198 198 try:
199 199 os.close(fd)
200 200 with open(tmpname, 'wt') as tmpfile:
201 201 tmpfile.write(strng)
202 202 cmd = "%s < %s" % (pager_cmd, tmpname)
203 203 # tmpfile needs to be closed for windows
204 204 if os.system(cmd):
205 205 retval = 1
206 206 else:
207 207 retval = None
208 208 finally:
209 209 os.remove(tmpname)
210 210 else:
211 211 try:
212 212 retval = None
213 213 # Emulate os.popen, but redirect stderr
214 214 proc = subprocess.Popen(pager_cmd,
215 215 shell=True,
216 216 stdin=subprocess.PIPE,
217 217 stderr=subprocess.DEVNULL
218 218 )
219 219 pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc)
220 220 try:
221 221 pager_encoding = pager.encoding or sys.stdout.encoding
222 222 pager.write(strng)
223 223 finally:
224 224 retval = pager.close()
225 225 except IOError as msg: # broken pipe when user quits
226 226 if msg.args == (32, 'Broken pipe'):
227 227 retval = None
228 228 else:
229 229 retval = 1
230 230 except OSError:
231 231 # Other strange problems, sometimes seen in Win2k/cygwin
232 232 retval = 1
233 233 if retval is not None:
234 234 page_dumb(strng,screen_lines=screen_lines)
235 235
236 236
237 237 def page(data, start=0, screen_lines=0, pager_cmd=None):
238 238 """Display content in a pager, piping through a pager after a certain length.
239 239
240 240 data can be a mime-bundle dict, supplying multiple representations,
241 241 keyed by mime-type, or text.
242 242
243 243 Pager is dispatched via the `show_in_pager` IPython hook.
244 244 If no hook is registered, `pager_page` will be used.
245 245 """
246 246 # Some routines may auto-compute start offsets incorrectly and pass a
247 247 # negative value. Offset to 0 for robustness.
248 248 start = max(0, start)
249 249
250 250 # first, try the hook
251 251 ip = get_ipython()
252 252 if ip:
253 253 try:
254 254 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
255 255 return
256 256 except TryNext:
257 257 pass
258 258
259 259 # fallback on default pager
260 260 return pager_page(data, start, screen_lines, pager_cmd)
261 261
262 262
263 263 def page_file(fname, start=0, pager_cmd=None):
264 264 """Page a file, using an optional pager command and starting line.
265 265 """
266 266
267 267 pager_cmd = get_pager_cmd(pager_cmd)
268 268 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
269 269
270 270 try:
271 271 if os.environ['TERM'] in ['emacs','dumb']:
272 272 raise EnvironmentError
273 273 system(pager_cmd + ' ' + fname)
274 274 except:
275 275 try:
276 276 if start > 0:
277 277 start -= 1
278 278 page(open(fname).read(),start)
279 279 except:
280 280 print('Unable to show file',repr(fname))
281 281
282 282
283 283 def get_pager_cmd(pager_cmd=None):
284 284 """Return a pager command.
285 285
286 286 Makes some attempts at finding an OS-correct one.
287 287 """
288 288 if os.name == 'posix':
289 289 default_pager_cmd = 'less -R' # -R for color control sequences
290 290 elif os.name in ['nt','dos']:
291 291 default_pager_cmd = 'type'
292 292
293 293 if pager_cmd is None:
294 294 try:
295 295 pager_cmd = os.environ['PAGER']
296 296 except:
297 297 pager_cmd = default_pager_cmd
298 298
299 299 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
300 300 pager_cmd += ' -R'
301 301
302 302 return pager_cmd
303 303
304 304
305 305 def get_pager_start(pager, start):
306 306 """Return the string for paging files with an offset.
307 307
308 308 This is the '+N' argument which less and more (under Unix) accept.
309 309 """
310 310
311 311 if pager in ['less','more']:
312 312 if start:
313 313 start_string = '+' + str(start)
314 314 else:
315 315 start_string = ''
316 316 else:
317 317 start_string = ''
318 318 return start_string
319 319
320 320
321 321 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
322 322 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
323 323 import msvcrt
324 324 def page_more():
325 325 """ Smart pausing between pages
326 326
327 327 @return: True if need print more lines, False if quit
328 328 """
329 329 sys.stdout.write('---Return to continue, q to quit--- ')
330 330 ans = msvcrt.getwch()
331 331 if ans in ("q", "Q"):
332 332 result = False
333 333 else:
334 334 result = True
335 335 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
336 336 return result
337 337 else:
338 338 def page_more():
339 339 ans = py3compat.input('---Return to continue, q to quit--- ')
340 340 if ans.lower().startswith('q'):
341 341 return False
342 342 else:
343 343 return True
@@ -1,419 +1,419 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Pylab (matplotlib) support utilities."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO
8 8
9 9 from IPython.core.display import _pngxy
10 10 from IPython.utils.decorators import flag_calls
11 11
12 12 # If user specifies a GUI, that dictates the backend, otherwise we read the
13 13 # user's mpl default from the mpl rc structure
14 14 backends = {'tk': 'TkAgg',
15 15 'gtk': 'GTKAgg',
16 16 'gtk3': 'GTK3Agg',
17 17 'wx': 'WXAgg',
18 18 'qt4': 'Qt4Agg',
19 19 'qt5': 'Qt5Agg',
20 20 'qt': 'Qt5Agg',
21 21 'osx': 'MacOSX',
22 22 'nbagg': 'nbAgg',
23 23 'notebook': 'nbAgg',
24 24 'agg': 'agg',
25 25 'svg': 'svg',
26 26 'pdf': 'pdf',
27 27 'ps': 'ps',
28 28 'inline': 'module://ipykernel.pylab.backend_inline',
29 29 'ipympl': 'module://ipympl.backend_nbagg',
30 30 'widget': 'module://ipympl.backend_nbagg',
31 31 }
32 32
33 33 # We also need a reverse backends2guis mapping that will properly choose which
34 34 # GUI support to activate based on the desired matplotlib backend. For the
35 35 # most part it's just a reverse of the above dict, but we also need to add a
36 36 # few others that map to the same GUI manually:
37 37 backend2gui = dict(zip(backends.values(), backends.keys()))
38 38 # Our tests expect backend2gui to just return 'qt'
39 39 backend2gui['Qt4Agg'] = 'qt'
40 40 # In the reverse mapping, there are a few extra valid matplotlib backends that
41 41 # map to the same GUI support
42 42 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
43 43 backend2gui['GTK3Cairo'] = 'gtk3'
44 44 backend2gui['WX'] = 'wx'
45 45 backend2gui['CocoaAgg'] = 'osx'
46 46 # And some backends that don't need GUI integration
47 47 del backend2gui['nbAgg']
48 48 del backend2gui['agg']
49 49 del backend2gui['svg']
50 50 del backend2gui['pdf']
51 51 del backend2gui['ps']
52 52 del backend2gui['module://ipykernel.pylab.backend_inline']
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Matplotlib utilities
56 56 #-----------------------------------------------------------------------------
57 57
58 58
59 59 def getfigs(*fig_nums):
60 60 """Get a list of matplotlib figures by figure numbers.
61 61
62 62 If no arguments are given, all available figures are returned. If the
63 63 argument list contains references to invalid figures, a warning is printed
64 64 but the function continues pasting further figures.
65 65
66 66 Parameters
67 67 ----------
68 68 figs : tuple
69 69 A tuple of ints giving the figure numbers of the figures to return.
70 70 """
71 71 from matplotlib._pylab_helpers import Gcf
72 72 if not fig_nums:
73 73 fig_managers = Gcf.get_all_fig_managers()
74 74 return [fm.canvas.figure for fm in fig_managers]
75 75 else:
76 76 figs = []
77 77 for num in fig_nums:
78 78 f = Gcf.figs.get(num)
79 79 if f is None:
80 80 print('Warning: figure %s not available.' % num)
81 81 else:
82 82 figs.append(f.canvas.figure)
83 83 return figs
84 84
85 85
86 86 def figsize(sizex, sizey):
87 87 """Set the default figure size to be [sizex, sizey].
88 88
89 89 This is just an easy to remember, convenience wrapper that sets::
90 90
91 91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 92 """
93 93 import matplotlib
94 94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 95
96 96
97 97 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
98 98 """Print a figure to an image, and return the resulting file data
99 99
100 100 Returned data will be bytes unless ``fmt='svg'``,
101 101 in which case it will be unicode.
102 102
103 103 Any keyword args are passed to fig.canvas.print_figure,
104 104 such as ``quality`` or ``bbox_inches``.
105 105 """
106 106 # When there's an empty figure, we shouldn't return anything, otherwise we
107 107 # get big blank areas in the qt console.
108 108 if not fig.axes and not fig.lines:
109 109 return
110 110
111 111 dpi = fig.dpi
112 112 if fmt == 'retina':
113 113 dpi = dpi * 2
114 114 fmt = 'png'
115 115
116 116 # build keyword args
117 117 kw = {
118 118 "format":fmt,
119 119 "facecolor":fig.get_facecolor(),
120 120 "edgecolor":fig.get_edgecolor(),
121 121 "dpi":dpi,
122 122 "bbox_inches":bbox_inches,
123 123 }
124 124 # **kwargs get higher priority
125 125 kw.update(kwargs)
126 126
127 127 bytes_io = BytesIO()
128 128 if fig.canvas is None:
129 129 from matplotlib.backend_bases import FigureCanvasBase
130 130 FigureCanvasBase(fig)
131 131
132 132 fig.canvas.print_figure(bytes_io, **kw)
133 133 data = bytes_io.getvalue()
134 134 if fmt == 'svg':
135 135 data = data.decode('utf-8')
136 136 return data
137 137
138 138 def retina_figure(fig, **kwargs):
139 139 """format a figure as a pixel-doubled (retina) PNG"""
140 140 pngdata = print_figure(fig, fmt='retina', **kwargs)
141 141 # Make sure that retina_figure acts just like print_figure and returns
142 142 # None when the figure is empty.
143 143 if pngdata is None:
144 144 return
145 145 w, h = _pngxy(pngdata)
146 146 metadata = {"width": w//2, "height":h//2}
147 147 return pngdata, metadata
148 148
149 149 # We need a little factory function here to create the closure where
150 150 # safe_execfile can live.
151 151 def mpl_runner(safe_execfile):
152 152 """Factory to return a matplotlib-enabled runner for %run.
153 153
154 154 Parameters
155 155 ----------
156 156 safe_execfile : function
157 157 This must be a function with the same interface as the
158 158 :meth:`safe_execfile` method of IPython.
159 159
160 160 Returns
161 161 -------
162 162 A function suitable for use as the ``runner`` argument of the %run magic
163 163 function.
164 164 """
165 165
166 166 def mpl_execfile(fname,*where,**kw):
167 167 """matplotlib-aware wrapper around safe_execfile.
168 168
169 169 Its interface is identical to that of the :func:`execfile` builtin.
170 170
171 171 This is ultimately a call to execfile(), but wrapped in safeties to
172 172 properly handle interactive rendering."""
173 173
174 174 import matplotlib
175 175 import matplotlib.pyplot as plt
176 176
177 177 #print '*** Matplotlib runner ***' # dbg
178 178 # turn off rendering until end of script
179 179 is_interactive = matplotlib.rcParams['interactive']
180 180 matplotlib.interactive(False)
181 181 safe_execfile(fname,*where,**kw)
182 182 matplotlib.interactive(is_interactive)
183 183 # make rendering call now, if the user tried to do it
184 184 if plt.draw_if_interactive.called:
185 185 plt.draw()
186 186 plt.draw_if_interactive.called = False
187 187
188 188 # re-draw everything that is stale
189 189 try:
190 190 da = plt.draw_all
191 191 except AttributeError:
192 192 pass
193 193 else:
194 194 da()
195 195
196 196 return mpl_execfile
197 197
198 198
199 199 def _reshow_nbagg_figure(fig):
200 200 """reshow an nbagg figure"""
201 201 try:
202 202 reshow = fig.canvas.manager.reshow
203 except AttributeError:
204 raise NotImplementedError()
203 except AttributeError as e:
204 raise NotImplementedError() from e
205 205 else:
206 206 reshow()
207 207
208 208
209 209 def select_figure_formats(shell, formats, **kwargs):
210 210 """Select figure formats for the inline backend.
211 211
212 212 Parameters
213 213 ==========
214 214 shell : InteractiveShell
215 215 The main IPython instance.
216 216 formats : str or set
217 217 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
218 218 **kwargs : any
219 219 Extra keyword arguments to be passed to fig.canvas.print_figure.
220 220 """
221 221 import matplotlib
222 222 from matplotlib.figure import Figure
223 223
224 224 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
225 225 png_formatter = shell.display_formatter.formatters['image/png']
226 226 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
227 227 pdf_formatter = shell.display_formatter.formatters['application/pdf']
228 228
229 229 if isinstance(formats, str):
230 230 formats = {formats}
231 231 # cast in case of list / tuple
232 232 formats = set(formats)
233 233
234 234 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
235 235 mplbackend = matplotlib.get_backend().lower()
236 236 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
237 237 formatter = shell.display_formatter.ipython_display_formatter
238 238 formatter.for_type(Figure, _reshow_nbagg_figure)
239 239
240 240 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
241 241 bad = formats.difference(supported)
242 242 if bad:
243 243 bs = "%s" % ','.join([repr(f) for f in bad])
244 244 gs = "%s" % ','.join([repr(f) for f in supported])
245 245 raise ValueError("supported formats are: %s not %s" % (gs, bs))
246 246
247 247 if 'png' in formats:
248 248 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
249 249 if 'retina' in formats or 'png2x' in formats:
250 250 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
251 251 if 'jpg' in formats or 'jpeg' in formats:
252 252 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
253 253 if 'svg' in formats:
254 254 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
255 255 if 'pdf' in formats:
256 256 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
257 257
258 258 #-----------------------------------------------------------------------------
259 259 # Code for initializing matplotlib and importing pylab
260 260 #-----------------------------------------------------------------------------
261 261
262 262
263 263 def find_gui_and_backend(gui=None, gui_select=None):
264 264 """Given a gui string return the gui and mpl backend.
265 265
266 266 Parameters
267 267 ----------
268 268 gui : str
269 269 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
270 270 gui_select : str
271 271 Can be one of ('tk','gtk','wx','qt','qt4','inline').
272 272 This is any gui already selected by the shell.
273 273
274 274 Returns
275 275 -------
276 276 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
277 277 'WXAgg','Qt4Agg','module://ipykernel.pylab.backend_inline','agg').
278 278 """
279 279
280 280 import matplotlib
281 281
282 282 if gui and gui != 'auto':
283 283 # select backend based on requested gui
284 284 backend = backends[gui]
285 285 if gui == 'agg':
286 286 gui = None
287 287 else:
288 288 # We need to read the backend from the original data structure, *not*
289 289 # from mpl.rcParams, since a prior invocation of %matplotlib may have
290 290 # overwritten that.
291 291 # WARNING: this assumes matplotlib 1.1 or newer!!
292 292 backend = matplotlib.rcParamsOrig['backend']
293 293 # In this case, we need to find what the appropriate gui selection call
294 294 # should be for IPython, so we can activate inputhook accordingly
295 295 gui = backend2gui.get(backend, None)
296 296
297 297 # If we have already had a gui active, we need it and inline are the
298 298 # ones allowed.
299 299 if gui_select and gui != gui_select:
300 300 gui = gui_select
301 301 backend = backends[gui]
302 302
303 303 return gui, backend
304 304
305 305
306 306 def activate_matplotlib(backend):
307 307 """Activate the given backend and set interactive to True."""
308 308
309 309 import matplotlib
310 310 matplotlib.interactive(True)
311 311
312 312 # Matplotlib had a bug where even switch_backend could not force
313 313 # the rcParam to update. This needs to be set *before* the module
314 314 # magic of switch_backend().
315 315 matplotlib.rcParams['backend'] = backend
316 316
317 317 # Due to circular imports, pyplot may be only partially initialised
318 318 # when this function runs.
319 319 # So avoid needing matplotlib attribute-lookup to access pyplot.
320 320 from matplotlib import pyplot as plt
321 321
322 322 plt.switch_backend(backend)
323 323
324 324 plt.show._needmain = False
325 325 # We need to detect at runtime whether show() is called by the user.
326 326 # For this, we wrap it into a decorator which adds a 'called' flag.
327 327 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
328 328
329 329
330 330 def import_pylab(user_ns, import_all=True):
331 331 """Populate the namespace with pylab-related values.
332 332
333 333 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
334 334
335 335 Also imports a few names from IPython (figsize, display, getfigs)
336 336
337 337 """
338 338
339 339 # Import numpy as np/pyplot as plt are conventions we're trying to
340 340 # somewhat standardize on. Making them available to users by default
341 341 # will greatly help this.
342 342 s = ("import numpy\n"
343 343 "import matplotlib\n"
344 344 "from matplotlib import pylab, mlab, pyplot\n"
345 345 "np = numpy\n"
346 346 "plt = pyplot\n"
347 347 )
348 348 exec(s, user_ns)
349 349
350 350 if import_all:
351 351 s = ("from matplotlib.pylab import *\n"
352 352 "from numpy import *\n")
353 353 exec(s, user_ns)
354 354
355 355 # IPython symbols to add
356 356 user_ns['figsize'] = figsize
357 357 from IPython.display import display
358 358 # Add display and getfigs to the user's namespace
359 359 user_ns['display'] = display
360 360 user_ns['getfigs'] = getfigs
361 361
362 362
363 363 def configure_inline_support(shell, backend):
364 364 """Configure an IPython shell object for matplotlib use.
365 365
366 366 Parameters
367 367 ----------
368 368 shell : InteractiveShell instance
369 369
370 370 backend : matplotlib backend
371 371 """
372 372 # If using our svg payload backend, register the post-execution
373 373 # function that will pick up the results for display. This can only be
374 374 # done with access to the real shell object.
375 375
376 376 # Note: if we can't load the inline backend, then there's no point
377 377 # continuing (such as in terminal-only shells in environments without
378 378 # zeromq available).
379 379 try:
380 380 from ipykernel.pylab.backend_inline import InlineBackend
381 381 except ImportError:
382 382 return
383 383 import matplotlib
384 384
385 385 cfg = InlineBackend.instance(parent=shell)
386 386 cfg.shell = shell
387 387 if cfg not in shell.configurables:
388 388 shell.configurables.append(cfg)
389 389
390 390 if backend == backends['inline']:
391 391 from ipykernel.pylab.backend_inline import flush_figures
392 392 shell.events.register('post_execute', flush_figures)
393 393
394 394 # Save rcParams that will be overwrittern
395 395 shell._saved_rcParams = {}
396 396 for k in cfg.rc:
397 397 shell._saved_rcParams[k] = matplotlib.rcParams[k]
398 398 # load inline_rc
399 399 matplotlib.rcParams.update(cfg.rc)
400 400 new_backend_name = "inline"
401 401 else:
402 402 from ipykernel.pylab.backend_inline import flush_figures
403 403 try:
404 404 shell.events.unregister('post_execute', flush_figures)
405 405 except ValueError:
406 406 pass
407 407 if hasattr(shell, '_saved_rcParams'):
408 408 matplotlib.rcParams.update(shell._saved_rcParams)
409 409 del shell._saved_rcParams
410 410 new_backend_name = "other"
411 411
412 412 # only enable the formats once -> don't change the enabled formats (which the user may
413 413 # has changed) when getting another "%matplotlib inline" call.
414 414 # See https://github.com/ipython/ipykernel/issues/29
415 415 cur_backend = getattr(configure_inline_support, "current_backend", "unset")
416 416 if new_backend_name != cur_backend:
417 417 # Setup the default figure format
418 418 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
419 419 configure_inline_support.current_backend = new_backend_name
@@ -1,256 +1,256 b''
1 1 """Tests for pylab tools module.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from io import UnsupportedOperation, BytesIO
9 9
10 10 import matplotlib
11 11 matplotlib.use('Agg')
12 12 from matplotlib.figure import Figure
13 13
14 14 from nose import SkipTest
15 15 import nose.tools as nt
16 16
17 17 from matplotlib import pyplot as plt
18 18 import numpy as np
19 19
20 20 from IPython.core.getipython import get_ipython
21 21 from IPython.core.interactiveshell import InteractiveShell
22 22 from IPython.core.display import _PNG, _JPEG
23 23 from .. import pylabtools as pt
24 24
25 25 from IPython.testing import decorators as dec
26 26
27 27
28 28 def test_figure_to_svg():
29 29 # simple empty-figure test
30 30 fig = plt.figure()
31 31 nt.assert_equal(pt.print_figure(fig, 'svg'), None)
32 32
33 33 plt.close('all')
34 34
35 35 # simple check for at least svg-looking output
36 36 fig = plt.figure()
37 37 ax = fig.add_subplot(1,1,1)
38 38 ax.plot([1,2,3])
39 39 plt.draw()
40 40 svg = pt.print_figure(fig, 'svg')[:100].lower()
41 41 nt.assert_in(u'doctype svg', svg)
42 42
43 43 def _check_pil_jpeg_bytes():
44 44 """Skip if PIL can't write JPEGs to BytesIO objects"""
45 45 # PIL's JPEG plugin can't write to BytesIO objects
46 46 # Pillow fixes this
47 47 from PIL import Image
48 48 buf = BytesIO()
49 49 img = Image.new("RGB", (4,4))
50 50 try:
51 51 img.save(buf, 'jpeg')
52 52 except Exception as e:
53 53 ename = e.__class__.__name__
54 raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e))
54 raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
55 55
56 56 @dec.skip_without("PIL.Image")
57 57 def test_figure_to_jpeg():
58 58 _check_pil_jpeg_bytes()
59 59 # simple check for at least jpeg-looking output
60 60 fig = plt.figure()
61 61 ax = fig.add_subplot(1,1,1)
62 62 ax.plot([1,2,3])
63 63 plt.draw()
64 64 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
65 65 assert jpeg.startswith(_JPEG)
66 66
67 67 def test_retina_figure():
68 68 # simple empty-figure test
69 69 fig = plt.figure()
70 70 nt.assert_equal(pt.retina_figure(fig), None)
71 71 plt.close('all')
72 72
73 73 fig = plt.figure()
74 74 ax = fig.add_subplot(1,1,1)
75 75 ax.plot([1,2,3])
76 76 plt.draw()
77 77 png, md = pt.retina_figure(fig)
78 78 assert png.startswith(_PNG)
79 79 nt.assert_in('width', md)
80 80 nt.assert_in('height', md)
81 81
82 82 _fmt_mime_map = {
83 83 'png': 'image/png',
84 84 'jpeg': 'image/jpeg',
85 85 'pdf': 'application/pdf',
86 86 'retina': 'image/png',
87 87 'svg': 'image/svg+xml',
88 88 }
89 89
90 90 def test_select_figure_formats_str():
91 91 ip = get_ipython()
92 92 for fmt, active_mime in _fmt_mime_map.items():
93 93 pt.select_figure_formats(ip, fmt)
94 94 for mime, f in ip.display_formatter.formatters.items():
95 95 if mime == active_mime:
96 96 nt.assert_in(Figure, f)
97 97 else:
98 98 nt.assert_not_in(Figure, f)
99 99
100 100 def test_select_figure_formats_kwargs():
101 101 ip = get_ipython()
102 102 kwargs = dict(quality=10, bbox_inches='tight')
103 103 pt.select_figure_formats(ip, 'png', **kwargs)
104 104 formatter = ip.display_formatter.formatters['image/png']
105 105 f = formatter.lookup_by_type(Figure)
106 106 cell = f.__closure__[0].cell_contents
107 107 nt.assert_equal(cell, kwargs)
108 108
109 109 # check that the formatter doesn't raise
110 110 fig = plt.figure()
111 111 ax = fig.add_subplot(1,1,1)
112 112 ax.plot([1,2,3])
113 113 plt.draw()
114 114 formatter.enabled = True
115 115 png = formatter(fig)
116 116 assert png.startswith(_PNG)
117 117
118 118 def test_select_figure_formats_set():
119 119 ip = get_ipython()
120 120 for fmts in [
121 121 {'png', 'svg'},
122 122 ['png'],
123 123 ('jpeg', 'pdf', 'retina'),
124 124 {'svg'},
125 125 ]:
126 126 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
127 127 pt.select_figure_formats(ip, fmts)
128 128 for mime, f in ip.display_formatter.formatters.items():
129 129 if mime in active_mimes:
130 130 nt.assert_in(Figure, f)
131 131 else:
132 132 nt.assert_not_in(Figure, f)
133 133
134 134 def test_select_figure_formats_bad():
135 135 ip = get_ipython()
136 136 with nt.assert_raises(ValueError):
137 137 pt.select_figure_formats(ip, 'foo')
138 138 with nt.assert_raises(ValueError):
139 139 pt.select_figure_formats(ip, {'png', 'foo'})
140 140 with nt.assert_raises(ValueError):
141 141 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
142 142
143 143 def test_import_pylab():
144 144 ns = {}
145 145 pt.import_pylab(ns, import_all=False)
146 146 nt.assert_true('plt' in ns)
147 147 nt.assert_equal(ns['np'], np)
148 148
149 149 class TestPylabSwitch(object):
150 150 class Shell(InteractiveShell):
151 151 def enable_gui(self, gui):
152 152 pass
153 153
154 154 def setup(self):
155 155 import matplotlib
156 156 def act_mpl(backend):
157 157 matplotlib.rcParams['backend'] = backend
158 158
159 159 # Save rcParams since they get modified
160 160 self._saved_rcParams = matplotlib.rcParams
161 161 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
162 162 matplotlib.rcParams = dict(backend='Qt4Agg')
163 163 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
164 164
165 165 # Mock out functions
166 166 self._save_am = pt.activate_matplotlib
167 167 pt.activate_matplotlib = act_mpl
168 168 self._save_ip = pt.import_pylab
169 169 pt.import_pylab = lambda *a,**kw:None
170 170 self._save_cis = pt.configure_inline_support
171 171 pt.configure_inline_support = lambda *a,**kw:None
172 172
173 173 def teardown(self):
174 174 pt.activate_matplotlib = self._save_am
175 175 pt.import_pylab = self._save_ip
176 176 pt.configure_inline_support = self._save_cis
177 177 import matplotlib
178 178 matplotlib.rcParams = self._saved_rcParams
179 179 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
180 180
181 181 def test_qt(self):
182 182 s = self.Shell()
183 183 gui, backend = s.enable_matplotlib(None)
184 184 nt.assert_equal(gui, 'qt')
185 185 nt.assert_equal(s.pylab_gui_select, 'qt')
186 186
187 187 gui, backend = s.enable_matplotlib('inline')
188 188 nt.assert_equal(gui, 'inline')
189 189 nt.assert_equal(s.pylab_gui_select, 'qt')
190 190
191 191 gui, backend = s.enable_matplotlib('qt')
192 192 nt.assert_equal(gui, 'qt')
193 193 nt.assert_equal(s.pylab_gui_select, 'qt')
194 194
195 195 gui, backend = s.enable_matplotlib('inline')
196 196 nt.assert_equal(gui, 'inline')
197 197 nt.assert_equal(s.pylab_gui_select, 'qt')
198 198
199 199 gui, backend = s.enable_matplotlib()
200 200 nt.assert_equal(gui, 'qt')
201 201 nt.assert_equal(s.pylab_gui_select, 'qt')
202 202
203 203 def test_inline(self):
204 204 s = self.Shell()
205 205 gui, backend = s.enable_matplotlib('inline')
206 206 nt.assert_equal(gui, 'inline')
207 207 nt.assert_equal(s.pylab_gui_select, None)
208 208
209 209 gui, backend = s.enable_matplotlib('inline')
210 210 nt.assert_equal(gui, 'inline')
211 211 nt.assert_equal(s.pylab_gui_select, None)
212 212
213 213 gui, backend = s.enable_matplotlib('qt')
214 214 nt.assert_equal(gui, 'qt')
215 215 nt.assert_equal(s.pylab_gui_select, 'qt')
216 216
217 217 def test_inline_twice(self):
218 218 "Using '%matplotlib inline' twice should not reset formatters"
219 219
220 220 ip = self.Shell()
221 221 gui, backend = ip.enable_matplotlib('inline')
222 222 nt.assert_equal(gui, 'inline')
223 223
224 224 fmts = {'png'}
225 225 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
226 226 pt.select_figure_formats(ip, fmts)
227 227
228 228 gui, backend = ip.enable_matplotlib('inline')
229 229 nt.assert_equal(gui, 'inline')
230 230
231 231 for mime, f in ip.display_formatter.formatters.items():
232 232 if mime in active_mimes:
233 233 nt.assert_in(Figure, f)
234 234 else:
235 235 nt.assert_not_in(Figure, f)
236 236
237 237 def test_qt_gtk(self):
238 238 s = self.Shell()
239 239 gui, backend = s.enable_matplotlib('qt')
240 240 nt.assert_equal(gui, 'qt')
241 241 nt.assert_equal(s.pylab_gui_select, 'qt')
242 242
243 243 gui, backend = s.enable_matplotlib('gtk')
244 244 nt.assert_equal(gui, 'qt')
245 245 nt.assert_equal(s.pylab_gui_select, 'qt')
246 246
247 247
248 248 def test_no_gui_backends():
249 249 for k in ['agg', 'svg', 'pdf', 'ps']:
250 250 assert k not in pt.backend2gui
251 251
252 252
253 253 def test_figure_no_canvas():
254 254 fig = Figure()
255 255 fig.canvas = None
256 256 pt.print_figure(fig)
@@ -1,601 +1,601 b''
1 1 # encoding: utf-8
2 2 """Tests for code execution (%run and related), which is particularly tricky.
3 3
4 4 Because of how %run manages namespaces, and the fact that we are trying here to
5 5 verify subtle object deletion and reference counting issues, the %run tests
6 6 will be kept in this separate file. This makes it easier to aggregate in one
7 7 place the tricks needed to handle it; most other magics are much easier to test
8 8 and we do so in a common test_magic file.
9 9
10 10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
11 11 as otherwise it may influence later tests.
12 12 """
13 13
14 14 # Copyright (c) IPython Development Team.
15 15 # Distributed under the terms of the Modified BSD License.
16 16
17 17
18 18
19 19 import functools
20 20 import os
21 21 from os.path import join as pjoin
22 22 import random
23 23 import string
24 24 import sys
25 25 import textwrap
26 26 import unittest
27 27 from unittest.mock import patch
28 28
29 29 import nose.tools as nt
30 30 from nose import SkipTest
31 31
32 32 from IPython.testing import decorators as dec
33 33 from IPython.testing import tools as tt
34 34 from IPython.utils.io import capture_output
35 35 from IPython.utils.tempdir import TemporaryDirectory
36 36 from IPython.core import debugger
37 37
38 38 def doctest_refbug():
39 39 """Very nasty problem with references held by multiple runs of a script.
40 40 See: https://github.com/ipython/ipython/issues/141
41 41
42 42 In [1]: _ip.clear_main_mod_cache()
43 43 # random
44 44
45 45 In [2]: %run refbug
46 46
47 47 In [3]: call_f()
48 48 lowercased: hello
49 49
50 50 In [4]: %run refbug
51 51
52 52 In [5]: call_f()
53 53 lowercased: hello
54 54 lowercased: hello
55 55 """
56 56
57 57
58 58 def doctest_run_builtins():
59 59 r"""Check that %run doesn't damage __builtins__.
60 60
61 61 In [1]: import tempfile
62 62
63 63 In [2]: bid1 = id(__builtins__)
64 64
65 65 In [3]: fname = tempfile.mkstemp('.py')[1]
66 66
67 67 In [3]: f = open(fname,'w')
68 68
69 69 In [4]: dummy= f.write('pass\n')
70 70
71 71 In [5]: f.flush()
72 72
73 73 In [6]: t1 = type(__builtins__)
74 74
75 75 In [7]: %run $fname
76 76
77 77 In [7]: f.close()
78 78
79 79 In [8]: bid2 = id(__builtins__)
80 80
81 81 In [9]: t2 = type(__builtins__)
82 82
83 83 In [10]: t1 == t2
84 84 Out[10]: True
85 85
86 86 In [10]: bid1 == bid2
87 87 Out[10]: True
88 88
89 89 In [12]: try:
90 90 ....: os.unlink(fname)
91 91 ....: except:
92 92 ....: pass
93 93 ....:
94 94 """
95 95
96 96
97 97 def doctest_run_option_parser():
98 98 r"""Test option parser in %run.
99 99
100 100 In [1]: %run print_argv.py
101 101 []
102 102
103 103 In [2]: %run print_argv.py print*.py
104 104 ['print_argv.py']
105 105
106 106 In [3]: %run -G print_argv.py print*.py
107 107 ['print*.py']
108 108
109 109 """
110 110
111 111
112 112 @dec.skip_win32
113 113 def doctest_run_option_parser_for_posix():
114 114 r"""Test option parser in %run (Linux/OSX specific).
115 115
116 116 You need double quote to escape glob in POSIX systems:
117 117
118 118 In [1]: %run print_argv.py print\\*.py
119 119 ['print*.py']
120 120
121 121 You can't use quote to escape glob in POSIX systems:
122 122
123 123 In [2]: %run print_argv.py 'print*.py'
124 124 ['print_argv.py']
125 125
126 126 """
127 127
128 128
129 129 @dec.skip_if_not_win32
130 130 def doctest_run_option_parser_for_windows():
131 131 r"""Test option parser in %run (Windows specific).
132 132
133 133 In Windows, you can't escape ``*` `by backslash:
134 134
135 135 In [1]: %run print_argv.py print\\*.py
136 136 ['print\\*.py']
137 137
138 138 You can use quote to escape glob:
139 139
140 140 In [2]: %run print_argv.py 'print*.py'
141 141 ['print*.py']
142 142
143 143 """
144 144
145 145
146 146 def doctest_reset_del():
147 147 """Test that resetting doesn't cause errors in __del__ methods.
148 148
149 149 In [2]: class A(object):
150 150 ...: def __del__(self):
151 151 ...: print(str("Hi"))
152 152 ...:
153 153
154 154 In [3]: a = A()
155 155
156 156 In [4]: get_ipython().reset()
157 157 Hi
158 158
159 159 In [5]: 1+1
160 160 Out[5]: 2
161 161 """
162 162
163 163 # For some tests, it will be handy to organize them in a class with a common
164 164 # setup that makes a temp file
165 165
166 166 class TestMagicRunPass(tt.TempFileMixin):
167 167
168 168 def setUp(self):
169 169 content = "a = [1,2,3]\nb = 1"
170 170 self.mktmp(content)
171 171
172 172 def run_tmpfile(self):
173 173 _ip = get_ipython()
174 174 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
175 175 # See below and ticket https://bugs.launchpad.net/bugs/366353
176 176 _ip.magic('run %s' % self.fname)
177 177
178 178 def run_tmpfile_p(self):
179 179 _ip = get_ipython()
180 180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
181 181 # See below and ticket https://bugs.launchpad.net/bugs/366353
182 182 _ip.magic('run -p %s' % self.fname)
183 183
184 184 def test_builtins_id(self):
185 185 """Check that %run doesn't damage __builtins__ """
186 186 _ip = get_ipython()
187 187 # Test that the id of __builtins__ is not modified by %run
188 188 bid1 = id(_ip.user_ns['__builtins__'])
189 189 self.run_tmpfile()
190 190 bid2 = id(_ip.user_ns['__builtins__'])
191 191 nt.assert_equal(bid1, bid2)
192 192
193 193 def test_builtins_type(self):
194 194 """Check that the type of __builtins__ doesn't change with %run.
195 195
196 196 However, the above could pass if __builtins__ was already modified to
197 197 be a dict (it should be a module) by a previous use of %run. So we
198 198 also check explicitly that it really is a module:
199 199 """
200 200 _ip = get_ipython()
201 201 self.run_tmpfile()
202 202 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
203 203
204 204 def test_run_profile( self ):
205 205 """Test that the option -p, which invokes the profiler, do not
206 206 crash by invoking execfile"""
207 207 self.run_tmpfile_p()
208 208
209 209 def test_run_debug_twice(self):
210 210 # https://github.com/ipython/ipython/issues/10028
211 211 _ip = get_ipython()
212 212 with tt.fake_input(['c']):
213 213 _ip.magic('run -d %s' % self.fname)
214 214 with tt.fake_input(['c']):
215 215 _ip.magic('run -d %s' % self.fname)
216 216
217 217 def test_run_debug_twice_with_breakpoint(self):
218 218 """Make a valid python temp file."""
219 219 _ip = get_ipython()
220 220 with tt.fake_input(['b 2', 'c', 'c']):
221 221 _ip.magic('run -d %s' % self.fname)
222 222
223 223 with tt.fake_input(['c']):
224 224 with tt.AssertNotPrints('KeyError'):
225 225 _ip.magic('run -d %s' % self.fname)
226 226
227 227
228 228 class TestMagicRunSimple(tt.TempFileMixin):
229 229
230 230 def test_simpledef(self):
231 231 """Test that simple class definitions work."""
232 232 src = ("class foo: pass\n"
233 233 "def f(): return foo()")
234 234 self.mktmp(src)
235 235 _ip.magic('run %s' % self.fname)
236 236 _ip.run_cell('t = isinstance(f(), foo)')
237 237 nt.assert_true(_ip.user_ns['t'])
238 238
239 239 def test_obj_del(self):
240 240 """Test that object's __del__ methods are called on exit."""
241 241 if sys.platform == 'win32':
242 242 try:
243 243 import win32api
244 except ImportError:
245 raise SkipTest("Test requires pywin32")
244 except ImportError as e:
245 raise SkipTest("Test requires pywin32") from e
246 246 src = ("class A(object):\n"
247 247 " def __del__(self):\n"
248 248 " print('object A deleted')\n"
249 249 "a = A()\n")
250 250 self.mktmp(src)
251 251 err = None
252 252 tt.ipexec_validate(self.fname, 'object A deleted', err)
253 253
254 254 def test_aggressive_namespace_cleanup(self):
255 255 """Test that namespace cleanup is not too aggressive GH-238
256 256
257 257 Returning from another run magic deletes the namespace"""
258 258 # see ticket https://github.com/ipython/ipython/issues/238
259 259
260 260 with tt.TempFileMixin() as empty:
261 261 empty.mktmp('')
262 262 # On Windows, the filename will have \users in it, so we need to use the
263 263 # repr so that the \u becomes \\u.
264 264 src = ("ip = get_ipython()\n"
265 265 "for i in range(5):\n"
266 266 " try:\n"
267 267 " ip.magic(%r)\n"
268 268 " except NameError as e:\n"
269 269 " print(i)\n"
270 270 " break\n" % ('run ' + empty.fname))
271 271 self.mktmp(src)
272 272 _ip.magic('run %s' % self.fname)
273 273 _ip.run_cell('ip == get_ipython()')
274 274 nt.assert_equal(_ip.user_ns['i'], 4)
275 275
276 276 def test_run_second(self):
277 277 """Test that running a second file doesn't clobber the first, gh-3547
278 278 """
279 279 self.mktmp("avar = 1\n"
280 280 "def afunc():\n"
281 281 " return avar\n")
282 282
283 283 with tt.TempFileMixin() as empty:
284 284 empty.mktmp("")
285 285
286 286 _ip.magic('run %s' % self.fname)
287 287 _ip.magic('run %s' % empty.fname)
288 288 nt.assert_equal(_ip.user_ns['afunc'](), 1)
289 289
290 290 @dec.skip_win32
291 291 def test_tclass(self):
292 292 mydir = os.path.dirname(__file__)
293 293 tc = os.path.join(mydir, 'tclass')
294 294 src = ("%%run '%s' C-first\n"
295 295 "%%run '%s' C-second\n"
296 296 "%%run '%s' C-third\n") % (tc, tc, tc)
297 297 self.mktmp(src, '.ipy')
298 298 out = """\
299 299 ARGV 1-: ['C-first']
300 300 ARGV 1-: ['C-second']
301 301 tclass.py: deleting object: C-first
302 302 ARGV 1-: ['C-third']
303 303 tclass.py: deleting object: C-second
304 304 tclass.py: deleting object: C-third
305 305 """
306 306 err = None
307 307 tt.ipexec_validate(self.fname, out, err)
308 308
309 309 def test_run_i_after_reset(self):
310 310 """Check that %run -i still works after %reset (gh-693)"""
311 311 src = "yy = zz\n"
312 312 self.mktmp(src)
313 313 _ip.run_cell("zz = 23")
314 314 try:
315 315 _ip.magic('run -i %s' % self.fname)
316 316 nt.assert_equal(_ip.user_ns['yy'], 23)
317 317 finally:
318 318 _ip.magic('reset -f')
319 319
320 320 _ip.run_cell("zz = 23")
321 321 try:
322 322 _ip.magic('run -i %s' % self.fname)
323 323 nt.assert_equal(_ip.user_ns['yy'], 23)
324 324 finally:
325 325 _ip.magic('reset -f')
326 326
327 327 def test_unicode(self):
328 328 """Check that files in odd encodings are accepted."""
329 329 mydir = os.path.dirname(__file__)
330 330 na = os.path.join(mydir, 'nonascii.py')
331 331 _ip.magic('run "%s"' % na)
332 332 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
333 333
334 334 def test_run_py_file_attribute(self):
335 335 """Test handling of `__file__` attribute in `%run <file>.py`."""
336 336 src = "t = __file__\n"
337 337 self.mktmp(src)
338 338 _missing = object()
339 339 file1 = _ip.user_ns.get('__file__', _missing)
340 340 _ip.magic('run %s' % self.fname)
341 341 file2 = _ip.user_ns.get('__file__', _missing)
342 342
343 343 # Check that __file__ was equal to the filename in the script's
344 344 # namespace.
345 345 nt.assert_equal(_ip.user_ns['t'], self.fname)
346 346
347 347 # Check that __file__ was not leaked back into user_ns.
348 348 nt.assert_equal(file1, file2)
349 349
350 350 def test_run_ipy_file_attribute(self):
351 351 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
352 352 src = "t = __file__\n"
353 353 self.mktmp(src, ext='.ipy')
354 354 _missing = object()
355 355 file1 = _ip.user_ns.get('__file__', _missing)
356 356 _ip.magic('run %s' % self.fname)
357 357 file2 = _ip.user_ns.get('__file__', _missing)
358 358
359 359 # Check that __file__ was equal to the filename in the script's
360 360 # namespace.
361 361 nt.assert_equal(_ip.user_ns['t'], self.fname)
362 362
363 363 # Check that __file__ was not leaked back into user_ns.
364 364 nt.assert_equal(file1, file2)
365 365
366 366 def test_run_formatting(self):
367 367 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
368 368 src = "pass"
369 369 self.mktmp(src)
370 370 _ip.magic('run -t -N 1 %s' % self.fname)
371 371 _ip.magic('run -t -N 10 %s' % self.fname)
372 372
373 373 def test_ignore_sys_exit(self):
374 374 """Test the -e option to ignore sys.exit()"""
375 375 src = "import sys; sys.exit(1)"
376 376 self.mktmp(src)
377 377 with tt.AssertPrints('SystemExit'):
378 378 _ip.magic('run %s' % self.fname)
379 379
380 380 with tt.AssertNotPrints('SystemExit'):
381 381 _ip.magic('run -e %s' % self.fname)
382 382
383 383 def test_run_nb(self):
384 384 """Test %run notebook.ipynb"""
385 385 from nbformat import v4, writes
386 386 nb = v4.new_notebook(
387 387 cells=[
388 388 v4.new_markdown_cell("The Ultimate Question of Everything"),
389 389 v4.new_code_cell("answer=42")
390 390 ]
391 391 )
392 392 src = writes(nb, version=4)
393 393 self.mktmp(src, ext='.ipynb')
394 394
395 395 _ip.magic("run %s" % self.fname)
396 396
397 397 nt.assert_equal(_ip.user_ns['answer'], 42)
398 398
399 399 def test_run_nb_error(self):
400 400 """Test %run notebook.ipynb error"""
401 401 from nbformat import v4, writes
402 402 # %run when a file name isn't provided
403 403 nt.assert_raises(Exception, _ip.magic, "run")
404 404
405 405 # %run when a file doesn't exist
406 406 nt.assert_raises(Exception, _ip.magic, "run foobar.ipynb")
407 407
408 408 # %run on a notebook with an error
409 409 nb = v4.new_notebook(
410 410 cells=[
411 411 v4.new_code_cell("0/0")
412 412 ]
413 413 )
414 414 src = writes(nb, version=4)
415 415 self.mktmp(src, ext='.ipynb')
416 416 nt.assert_raises(Exception, _ip.magic, "run %s" % self.fname)
417 417
418 418 def test_file_options(self):
419 419 src = ('import sys\n'
420 420 'a = " ".join(sys.argv[1:])\n')
421 421 self.mktmp(src)
422 422 test_opts = '-x 3 --verbose'
423 423 _ip.run_line_magic("run", '{0} {1}'.format(self.fname, test_opts))
424 424 nt.assert_equal(_ip.user_ns['a'], test_opts)
425 425
426 426
427 427 class TestMagicRunWithPackage(unittest.TestCase):
428 428
429 429 def writefile(self, name, content):
430 430 path = os.path.join(self.tempdir.name, name)
431 431 d = os.path.dirname(path)
432 432 if not os.path.isdir(d):
433 433 os.makedirs(d)
434 434 with open(path, 'w') as f:
435 435 f.write(textwrap.dedent(content))
436 436
437 437 def setUp(self):
438 438 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
439 439 """Temporary (probably) valid python package name."""
440 440
441 441 self.value = int(random.random() * 10000)
442 442
443 443 self.tempdir = TemporaryDirectory()
444 444 self.__orig_cwd = os.getcwd()
445 445 sys.path.insert(0, self.tempdir.name)
446 446
447 447 self.writefile(os.path.join(package, '__init__.py'), '')
448 448 self.writefile(os.path.join(package, 'sub.py'), """
449 449 x = {0!r}
450 450 """.format(self.value))
451 451 self.writefile(os.path.join(package, 'relative.py'), """
452 452 from .sub import x
453 453 """)
454 454 self.writefile(os.path.join(package, 'absolute.py'), """
455 455 from {0}.sub import x
456 456 """.format(package))
457 457 self.writefile(os.path.join(package, 'args.py'), """
458 458 import sys
459 459 a = " ".join(sys.argv[1:])
460 460 """.format(package))
461 461
462 462 def tearDown(self):
463 463 os.chdir(self.__orig_cwd)
464 464 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
465 465 self.tempdir.cleanup()
466 466
467 467 def check_run_submodule(self, submodule, opts=''):
468 468 _ip.user_ns.pop('x', None)
469 469 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
470 470 self.assertEqual(_ip.user_ns['x'], self.value,
471 471 'Variable `x` is not loaded from module `{0}`.'
472 472 .format(submodule))
473 473
474 474 def test_run_submodule_with_absolute_import(self):
475 475 self.check_run_submodule('absolute')
476 476
477 477 def test_run_submodule_with_relative_import(self):
478 478 """Run submodule that has a relative import statement (#2727)."""
479 479 self.check_run_submodule('relative')
480 480
481 481 def test_prun_submodule_with_absolute_import(self):
482 482 self.check_run_submodule('absolute', '-p')
483 483
484 484 def test_prun_submodule_with_relative_import(self):
485 485 self.check_run_submodule('relative', '-p')
486 486
487 487 def with_fake_debugger(func):
488 488 @functools.wraps(func)
489 489 def wrapper(*args, **kwds):
490 490 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
491 491 return func(*args, **kwds)
492 492 return wrapper
493 493
494 494 @with_fake_debugger
495 495 def test_debug_run_submodule_with_absolute_import(self):
496 496 self.check_run_submodule('absolute', '-d')
497 497
498 498 @with_fake_debugger
499 499 def test_debug_run_submodule_with_relative_import(self):
500 500 self.check_run_submodule('relative', '-d')
501 501
502 502 def test_module_options(self):
503 503 _ip.user_ns.pop('a', None)
504 504 test_opts = '-x abc -m test'
505 505 _ip.run_line_magic('run', '-m {0}.args {1}'.format(self.package, test_opts))
506 506 nt.assert_equal(_ip.user_ns['a'], test_opts)
507 507
508 508 def test_module_options_with_separator(self):
509 509 _ip.user_ns.pop('a', None)
510 510 test_opts = '-x abc -m test'
511 511 _ip.run_line_magic('run', '-m {0}.args -- {1}'.format(self.package, test_opts))
512 512 nt.assert_equal(_ip.user_ns['a'], test_opts)
513 513
514 514 def test_run__name__():
515 515 with TemporaryDirectory() as td:
516 516 path = pjoin(td, 'foo.py')
517 517 with open(path, 'w') as f:
518 518 f.write("q = __name__")
519 519
520 520 _ip.user_ns.pop('q', None)
521 521 _ip.magic('run {}'.format(path))
522 522 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
523 523
524 524 _ip.magic('run -n {}'.format(path))
525 525 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
526 526
527 527 try:
528 528 _ip.magic('run -i -n {}'.format(path))
529 529 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
530 530 finally:
531 531 _ip.magic('reset -f')
532 532
533 533
534 534 def test_run_tb():
535 535 """Test traceback offset in %run"""
536 536 with TemporaryDirectory() as td:
537 537 path = pjoin(td, 'foo.py')
538 538 with open(path, 'w') as f:
539 539 f.write('\n'.join([
540 540 "def foo():",
541 541 " return bar()",
542 542 "def bar():",
543 543 " raise RuntimeError('hello!')",
544 544 "foo()",
545 545 ]))
546 546 with capture_output() as io:
547 547 _ip.magic('run {}'.format(path))
548 548 out = io.stdout
549 549 nt.assert_not_in("execfile", out)
550 550 nt.assert_in("RuntimeError", out)
551 551 nt.assert_equal(out.count("---->"), 3)
552 552 del ip.user_ns['bar']
553 553 del ip.user_ns['foo']
554 554
555 555
556 556 def test_multiprocessing_run():
557 557 """Set we can run mutiprocesgin without messing up up main namespace
558 558
559 559 Note that import `nose.tools as nt` mdify the value s
560 560 sys.module['__mp_main__'] so wee need to temporarily set it to None to test
561 561 the issue.
562 562 """
563 563 with TemporaryDirectory() as td:
564 564 mpm = sys.modules.get('__mp_main__')
565 565 assert mpm is not None
566 566 sys.modules['__mp_main__'] = None
567 567 try:
568 568 path = pjoin(td, 'test.py')
569 569 with open(path, 'w') as f:
570 570 f.write("import multiprocessing\nprint('hoy')")
571 571 with capture_output() as io:
572 572 _ip.run_line_magic('run', path)
573 573 _ip.run_cell("i_m_undefined")
574 574 out = io.stdout
575 575 nt.assert_in("hoy", out)
576 576 nt.assert_not_in("AttributeError", out)
577 577 nt.assert_in("NameError", out)
578 578 nt.assert_equal(out.count("---->"), 1)
579 579 except:
580 580 raise
581 581 finally:
582 582 sys.modules['__mp_main__'] = mpm
583 583
584 584 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
585 585 def test_script_tb():
586 586 """Test traceback offset in `ipython script.py`"""
587 587 with TemporaryDirectory() as td:
588 588 path = pjoin(td, 'foo.py')
589 589 with open(path, 'w') as f:
590 590 f.write('\n'.join([
591 591 "def foo():",
592 592 " return bar()",
593 593 "def bar():",
594 594 " raise RuntimeError('hello!')",
595 595 "foo()",
596 596 ]))
597 597 out, err = tt.ipexec(path)
598 598 nt.assert_not_in("execfile", out)
599 599 nt.assert_in("RuntimeError", out)
600 600 nt.assert_equal(out.count("---->"), 3)
601 601
@@ -1,233 +1,233 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 %store magic for lightweight persistence.
4 4
5 5 Stores variables, aliases and macros in IPython's database.
6 6
7 7 To automatically restore stored variables at startup, add this to your
8 8 :file:`ipython_config.py` file::
9 9
10 10 c.StoreMagics.autorestore = True
11 11 """
12 12
13 13 # Copyright (c) IPython Development Team.
14 14 # Distributed under the terms of the Modified BSD License.
15 15
16 16 import inspect, os, sys, textwrap
17 17
18 18 from IPython.core.error import UsageError
19 19 from IPython.core.magic import Magics, magics_class, line_magic
20 20 from traitlets import Bool
21 21
22 22
23 23 def restore_aliases(ip, alias=None):
24 24 staliases = ip.db.get('stored_aliases', {})
25 25 if alias is None:
26 26 for k,v in staliases.items():
27 27 #print "restore alias",k,v # dbg
28 28 #self.alias_table[k] = v
29 29 ip.alias_manager.define_alias(k,v)
30 30 else:
31 31 ip.alias_manager.define_alias(alias, staliases[alias])
32 32
33 33
34 34 def refresh_variables(ip):
35 35 db = ip.db
36 36 for key in db.keys('autorestore/*'):
37 37 # strip autorestore
38 38 justkey = os.path.basename(key)
39 39 try:
40 40 obj = db[key]
41 41 except KeyError:
42 42 print("Unable to restore variable '%s', ignoring (use %%store -d to forget!)" % justkey)
43 43 print("The error was:", sys.exc_info()[0])
44 44 else:
45 45 #print "restored",justkey,"=",obj #dbg
46 46 ip.user_ns[justkey] = obj
47 47
48 48
49 49 def restore_dhist(ip):
50 50 ip.user_ns['_dh'] = ip.db.get('dhist',[])
51 51
52 52
53 53 def restore_data(ip):
54 54 refresh_variables(ip)
55 55 restore_aliases(ip)
56 56 restore_dhist(ip)
57 57
58 58
59 59 @magics_class
60 60 class StoreMagics(Magics):
61 61 """Lightweight persistence for python variables.
62 62
63 63 Provides the %store magic."""
64 64
65 65 autorestore = Bool(False, help=
66 66 """If True, any %store-d variables will be automatically restored
67 67 when IPython starts.
68 68 """
69 69 ).tag(config=True)
70 70
71 71 def __init__(self, shell):
72 72 super(StoreMagics, self).__init__(shell=shell)
73 73 self.shell.configurables.append(self)
74 74 if self.autorestore:
75 75 restore_data(self.shell)
76 76
77 77 @line_magic
78 78 def store(self, parameter_s=''):
79 79 """Lightweight persistence for python variables.
80 80
81 81 Example::
82 82
83 83 In [1]: l = ['hello',10,'world']
84 84 In [2]: %store l
85 85 In [3]: exit
86 86
87 87 (IPython session is closed and started again...)
88 88
89 89 ville@badger:~$ ipython
90 90 In [1]: l
91 91 NameError: name 'l' is not defined
92 92 In [2]: %store -r
93 93 In [3]: l
94 94 Out[3]: ['hello', 10, 'world']
95 95
96 96 Usage:
97 97
98 98 * ``%store`` - Show list of all variables and their current
99 99 values
100 100 * ``%store spam bar`` - Store the *current* value of the variables spam
101 101 and bar to disk
102 102 * ``%store -d spam`` - Remove the variable and its value from storage
103 103 * ``%store -z`` - Remove all variables from storage
104 104 * ``%store -r`` - Refresh all variables, aliases and directory history
105 105 from store (overwrite current vals)
106 106 * ``%store -r spam bar`` - Refresh specified variables and aliases from store
107 107 (delete current val)
108 108 * ``%store foo >a.txt`` - Store value of foo to new file a.txt
109 109 * ``%store foo >>a.txt`` - Append value of foo to file a.txt
110 110
111 111 It should be noted that if you change the value of a variable, you
112 112 need to %store it again if you want to persist the new value.
113 113
114 114 Note also that the variables will need to be pickleable; most basic
115 115 python types can be safely %store'd.
116 116
117 117 Also aliases can be %store'd across sessions.
118 118 To remove an alias from the storage, use the %unalias magic.
119 119 """
120 120
121 121 opts,argsl = self.parse_options(parameter_s,'drz',mode='string')
122 122 args = argsl.split()
123 123 ip = self.shell
124 124 db = ip.db
125 125 # delete
126 126 if 'd' in opts:
127 127 try:
128 128 todel = args[0]
129 except IndexError:
130 raise UsageError('You must provide the variable to forget')
129 except IndexError as e:
130 raise UsageError('You must provide the variable to forget') from e
131 131 else:
132 132 try:
133 133 del db['autorestore/' + todel]
134 except:
135 raise UsageError("Can't delete variable '%s'" % todel)
134 except BaseException as e:
135 raise UsageError("Can't delete variable '%s'" % todel) from e
136 136 # reset
137 137 elif 'z' in opts:
138 138 for k in db.keys('autorestore/*'):
139 139 del db[k]
140 140
141 141 elif 'r' in opts:
142 142 if args:
143 143 for arg in args:
144 144 try:
145 145 obj = db['autorestore/' + arg]
146 146 except KeyError:
147 147 try:
148 148 restore_aliases(ip, alias=arg)
149 149 except KeyError:
150 150 print("no stored variable or alias %s" % arg)
151 151 else:
152 152 ip.user_ns[arg] = obj
153 153 else:
154 154 restore_data(ip)
155 155
156 156 # run without arguments -> list variables & values
157 157 elif not args:
158 158 vars = db.keys('autorestore/*')
159 159 vars.sort()
160 160 if vars:
161 161 size = max(map(len, vars))
162 162 else:
163 163 size = 0
164 164
165 165 print('Stored variables and their in-db values:')
166 166 fmt = '%-'+str(size)+'s -> %s'
167 167 get = db.get
168 168 for var in vars:
169 169 justkey = os.path.basename(var)
170 170 # print 30 first characters from every var
171 171 print(fmt % (justkey, repr(get(var, '<unavailable>'))[:50]))
172 172
173 173 # default action - store the variable
174 174 else:
175 175 # %store foo >file.txt or >>file.txt
176 176 if len(args) > 1 and args[1].startswith('>'):
177 177 fnam = os.path.expanduser(args[1].lstrip('>').lstrip())
178 178 if args[1].startswith('>>'):
179 179 fil = open(fnam, 'a')
180 180 else:
181 181 fil = open(fnam, 'w')
182 182 with fil:
183 183 obj = ip.ev(args[0])
184 184 print("Writing '%s' (%s) to file '%s'." % (args[0],
185 185 obj.__class__.__name__, fnam))
186 186
187 187 if not isinstance (obj, str):
188 188 from pprint import pprint
189 189 pprint(obj, fil)
190 190 else:
191 191 fil.write(obj)
192 192 if not obj.endswith('\n'):
193 193 fil.write('\n')
194 194
195 195 return
196 196
197 197 # %store foo
198 198 for arg in args:
199 199 try:
200 200 obj = ip.user_ns[arg]
201 201 except KeyError:
202 202 # it might be an alias
203 203 name = arg
204 204 try:
205 205 cmd = ip.alias_manager.retrieve_alias(name)
206 except ValueError:
207 raise UsageError("Unknown variable '%s'" % name)
206 except ValueError as e:
207 raise UsageError("Unknown variable '%s'" % name) from e
208 208
209 209 staliases = db.get('stored_aliases',{})
210 210 staliases[name] = cmd
211 211 db['stored_aliases'] = staliases
212 212 print("Alias stored: %s (%s)" % (name, cmd))
213 213 return
214 214
215 215 else:
216 216 modname = getattr(inspect.getmodule(obj), '__name__', '')
217 217 if modname == '__main__':
218 218 print(textwrap.dedent("""\
219 219 Warning:%s is %s
220 220 Proper storage of interactively declared classes (or instances
221 221 of those classes) is not possible! Only instances
222 222 of classes in real modules on file system can be %%store'd.
223 223 """ % (arg, obj) ))
224 224 return
225 225 #pickled = pickle.dumps(obj)
226 226 db[ 'autorestore/' + arg ] = obj
227 227 print("Stored '%s' (%s)" % (arg, obj.__class__.__name__))
228 228
229 229
230 230 def load_ipython_extension(ip):
231 231 """Load the extension in IPython."""
232 232 ip.register_magics(StoreMagics)
233 233
@@ -1,69 +1,69 b''
1 1 """ Utilities for accessing the platform's clipboard.
2 2 """
3 3
4 4 import subprocess
5 5
6 6 from IPython.core.error import TryNext
7 7 import IPython.utils.py3compat as py3compat
8 8
9 9 class ClipboardEmpty(ValueError):
10 10 pass
11 11
12 12 def win32_clipboard_get():
13 13 """ Get the current clipboard's text on Windows.
14 14
15 15 Requires Mark Hammond's pywin32 extensions.
16 16 """
17 17 try:
18 18 import win32clipboard
19 except ImportError:
19 except ImportError as e:
20 20 raise TryNext("Getting text from the clipboard requires the pywin32 "
21 "extensions: http://sourceforge.net/projects/pywin32/")
21 "extensions: http://sourceforge.net/projects/pywin32/") from e
22 22 win32clipboard.OpenClipboard()
23 23 try:
24 24 text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
25 25 except (TypeError, win32clipboard.error):
26 26 try:
27 27 text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
28 28 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
29 except (TypeError, win32clipboard.error):
30 raise ClipboardEmpty
29 except (TypeError, win32clipboard.error) as e:
30 raise ClipboardEmpty from e
31 31 finally:
32 32 win32clipboard.CloseClipboard()
33 33 return text
34 34
35 35 def osx_clipboard_get() -> str:
36 36 """ Get the clipboard's text on OS X.
37 37 """
38 38 p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'],
39 39 stdout=subprocess.PIPE)
40 40 bytes_, stderr = p.communicate()
41 41 # Text comes in with old Mac \r line endings. Change them to \n.
42 42 bytes_ = bytes_.replace(b'\r', b'\n')
43 43 text = py3compat.decode(bytes_)
44 44 return text
45 45
46 46 def tkinter_clipboard_get():
47 47 """ Get the clipboard's text using Tkinter.
48 48
49 49 This is the default on systems that are not Windows or OS X. It may
50 50 interfere with other UI toolkits and should be replaced with an
51 51 implementation that uses that toolkit.
52 52 """
53 53 try:
54 54 from tkinter import Tk, TclError
55 except ImportError:
56 raise TryNext("Getting text from the clipboard on this platform requires tkinter.")
55 except ImportError as e:
56 raise TryNext("Getting text from the clipboard on this platform requires tkinter.") from e
57 57
58 58 root = Tk()
59 59 root.withdraw()
60 60 try:
61 61 text = root.clipboard_get()
62 except TclError:
63 raise ClipboardEmpty
62 except TclError as e:
63 raise ClipboardEmpty from e
64 64 finally:
65 65 root.destroy()
66 66 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
67 67 return text
68 68
69 69
@@ -1,341 +1,341 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Provides a reload() function that acts recursively.
4 4
5 5 Python's normal :func:`python:reload` function only reloads the module that it's
6 6 passed. The :func:`reload` function in this module also reloads everything
7 7 imported from that module, which is useful when you're changing files deep
8 8 inside a package.
9 9
10 10 To use this as your default reload function, type this::
11 11
12 12 import builtins
13 13 from IPython.lib import deepreload
14 14 builtins.reload = deepreload.reload
15 15
16 16 A reference to the original :func:`python:reload` is stored in this module as
17 17 :data:`original_reload`, so you can restore it later.
18 18
19 19 This code is almost entirely based on knee.py, which is a Python
20 20 re-implementation of hierarchical module import.
21 21 """
22 22 #*****************************************************************************
23 23 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
24 24 #
25 25 # Distributed under the terms of the BSD License. The full license is in
26 26 # the file COPYING, distributed as part of this software.
27 27 #*****************************************************************************
28 28
29 29 import builtins as builtin_mod
30 30 from contextlib import contextmanager
31 31 import imp
32 32 import sys
33 33
34 34 from types import ModuleType
35 35 from warnings import warn
36 36 import types
37 37
38 38 original_import = builtin_mod.__import__
39 39
40 40 @contextmanager
41 41 def replace_import_hook(new_import):
42 42 saved_import = builtin_mod.__import__
43 43 builtin_mod.__import__ = new_import
44 44 try:
45 45 yield
46 46 finally:
47 47 builtin_mod.__import__ = saved_import
48 48
49 49 def get_parent(globals, level):
50 50 """
51 51 parent, name = get_parent(globals, level)
52 52
53 53 Return the package that an import is being performed in. If globals comes
54 54 from the module foo.bar.bat (not itself a package), this returns the
55 55 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
56 56 the package's entry in sys.modules is returned.
57 57
58 58 If globals doesn't come from a package or a module in a package, or a
59 59 corresponding entry is not found in sys.modules, None is returned.
60 60 """
61 61 orig_level = level
62 62
63 63 if not level or not isinstance(globals, dict):
64 64 return None, ''
65 65
66 66 pkgname = globals.get('__package__', None)
67 67
68 68 if pkgname is not None:
69 69 # __package__ is set, so use it
70 70 if not hasattr(pkgname, 'rindex'):
71 71 raise ValueError('__package__ set to non-string')
72 72 if len(pkgname) == 0:
73 73 if level > 0:
74 74 raise ValueError('Attempted relative import in non-package')
75 75 return None, ''
76 76 name = pkgname
77 77 else:
78 78 # __package__ not set, so figure it out and set it
79 79 if '__name__' not in globals:
80 80 return None, ''
81 81 modname = globals['__name__']
82 82
83 83 if '__path__' in globals:
84 84 # __path__ is set, so modname is already the package name
85 85 globals['__package__'] = name = modname
86 86 else:
87 87 # Normal module, so work out the package name if any
88 88 lastdot = modname.rfind('.')
89 89 if lastdot < 0 < level:
90 90 raise ValueError("Attempted relative import in non-package")
91 91 if lastdot < 0:
92 92 globals['__package__'] = None
93 93 return None, ''
94 94 globals['__package__'] = name = modname[:lastdot]
95 95
96 96 dot = len(name)
97 97 for x in range(level, 1, -1):
98 98 try:
99 99 dot = name.rindex('.', 0, dot)
100 except ValueError:
100 except ValueError as e:
101 101 raise ValueError("attempted relative import beyond top-level "
102 "package")
102 "package") from e
103 103 name = name[:dot]
104 104
105 105 try:
106 106 parent = sys.modules[name]
107 except:
107 except BaseException as e:
108 108 if orig_level < 1:
109 109 warn("Parent module '%.200s' not found while handling absolute "
110 110 "import" % name)
111 111 parent = None
112 112 else:
113 113 raise SystemError("Parent module '%.200s' not loaded, cannot "
114 "perform relative import" % name)
114 "perform relative import" % name) from e
115 115
116 116 # We expect, but can't guarantee, if parent != None, that:
117 117 # - parent.__name__ == name
118 118 # - parent.__dict__ is globals
119 119 # If this is violated... Who cares?
120 120 return parent, name
121 121
122 122 def load_next(mod, altmod, name, buf):
123 123 """
124 124 mod, name, buf = load_next(mod, altmod, name, buf)
125 125
126 126 altmod is either None or same as mod
127 127 """
128 128
129 129 if len(name) == 0:
130 130 # completely empty module name should only happen in
131 131 # 'from . import' (or '__import__("")')
132 132 return mod, None, buf
133 133
134 134 dot = name.find('.')
135 135 if dot == 0:
136 136 raise ValueError('Empty module name')
137 137
138 138 if dot < 0:
139 139 subname = name
140 140 next = None
141 141 else:
142 142 subname = name[:dot]
143 143 next = name[dot+1:]
144 144
145 145 if buf != '':
146 146 buf += '.'
147 147 buf += subname
148 148
149 149 result = import_submodule(mod, subname, buf)
150 150 if result is None and mod != altmod:
151 151 result = import_submodule(altmod, subname, subname)
152 152 if result is not None:
153 153 buf = subname
154 154
155 155 if result is None:
156 156 raise ImportError("No module named %.200s" % name)
157 157
158 158 return result, next, buf
159 159
160 160
161 161 # Need to keep track of what we've already reloaded to prevent cyclic evil
162 162 found_now = {}
163 163
164 164 def import_submodule(mod, subname, fullname):
165 165 """m = import_submodule(mod, subname, fullname)"""
166 166 # Require:
167 167 # if mod == None: subname == fullname
168 168 # else: mod.__name__ + "." + subname == fullname
169 169
170 170 global found_now
171 171 if fullname in found_now and fullname in sys.modules:
172 172 m = sys.modules[fullname]
173 173 else:
174 174 print('Reloading', fullname)
175 175 found_now[fullname] = 1
176 176 oldm = sys.modules.get(fullname, None)
177 177
178 178 if mod is None:
179 179 path = None
180 180 elif hasattr(mod, '__path__'):
181 181 path = mod.__path__
182 182 else:
183 183 return None
184 184
185 185 try:
186 186 # This appears to be necessary on Python 3, because imp.find_module()
187 187 # tries to import standard libraries (like io) itself, and we don't
188 188 # want them to be processed by our deep_import_hook.
189 189 with replace_import_hook(original_import):
190 190 fp, filename, stuff = imp.find_module(subname, path)
191 191 except ImportError:
192 192 return None
193 193
194 194 try:
195 195 m = imp.load_module(fullname, fp, filename, stuff)
196 196 except:
197 197 # load_module probably removed name from modules because of
198 198 # the error. Put back the original module object.
199 199 if oldm:
200 200 sys.modules[fullname] = oldm
201 201 raise
202 202 finally:
203 203 if fp: fp.close()
204 204
205 205 add_submodule(mod, m, fullname, subname)
206 206
207 207 return m
208 208
209 209 def add_submodule(mod, submod, fullname, subname):
210 210 """mod.{subname} = submod"""
211 211 if mod is None:
212 212 return #Nothing to do here.
213 213
214 214 if submod is None:
215 215 submod = sys.modules[fullname]
216 216
217 217 setattr(mod, subname, submod)
218 218
219 219 return
220 220
221 221 def ensure_fromlist(mod, fromlist, buf, recursive):
222 222 """Handle 'from module import a, b, c' imports."""
223 223 if not hasattr(mod, '__path__'):
224 224 return
225 225 for item in fromlist:
226 226 if not hasattr(item, 'rindex'):
227 227 raise TypeError("Item in ``from list'' not a string")
228 228 if item == '*':
229 229 if recursive:
230 230 continue # avoid endless recursion
231 231 try:
232 232 all = mod.__all__
233 233 except AttributeError:
234 234 pass
235 235 else:
236 236 ret = ensure_fromlist(mod, all, buf, 1)
237 237 if not ret:
238 238 return 0
239 239 elif not hasattr(mod, item):
240 240 import_submodule(mod, item, buf + '.' + item)
241 241
242 242 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
243 243 """Replacement for __import__()"""
244 244 parent, buf = get_parent(globals, level)
245 245
246 246 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
247 247
248 248 tail = head
249 249 while name:
250 250 tail, name, buf = load_next(tail, tail, name, buf)
251 251
252 252 # If tail is None, both get_parent and load_next found
253 253 # an empty module name: someone called __import__("") or
254 254 # doctored faulty bytecode
255 255 if tail is None:
256 256 raise ValueError('Empty module name')
257 257
258 258 if not fromlist:
259 259 return head
260 260
261 261 ensure_fromlist(tail, fromlist, buf, 0)
262 262 return tail
263 263
264 264 modules_reloading = {}
265 265
266 266 def deep_reload_hook(m):
267 267 """Replacement for reload()."""
268 268 # Hardcode this one as it would raise a NotImplementedError from the
269 269 # bowels of Python and screw up the import machinery after.
270 270 # unlike other imports the `exclude` list already in place is not enough.
271 271
272 272 if m is types:
273 273 return m
274 274 if not isinstance(m, ModuleType):
275 275 raise TypeError("reload() argument must be module")
276 276
277 277 name = m.__name__
278 278
279 279 if name not in sys.modules:
280 280 raise ImportError("reload(): module %.200s not in sys.modules" % name)
281 281
282 282 global modules_reloading
283 283 try:
284 284 return modules_reloading[name]
285 285 except:
286 286 modules_reloading[name] = m
287 287
288 288 dot = name.rfind('.')
289 289 if dot < 0:
290 290 subname = name
291 291 path = None
292 292 else:
293 293 try:
294 294 parent = sys.modules[name[:dot]]
295 except KeyError:
295 except KeyError as e:
296 296 modules_reloading.clear()
297 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
297 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot]) from e
298 298 subname = name[dot+1:]
299 299 path = getattr(parent, "__path__", None)
300 300
301 301 try:
302 302 # This appears to be necessary on Python 3, because imp.find_module()
303 303 # tries to import standard libraries (like io) itself, and we don't
304 304 # want them to be processed by our deep_import_hook.
305 305 with replace_import_hook(original_import):
306 306 fp, filename, stuff = imp.find_module(subname, path)
307 307 finally:
308 308 modules_reloading.clear()
309 309
310 310 try:
311 311 newm = imp.load_module(name, fp, filename, stuff)
312 312 except:
313 313 # load_module probably removed name from modules because of
314 314 # the error. Put back the original module object.
315 315 sys.modules[name] = m
316 316 raise
317 317 finally:
318 318 if fp: fp.close()
319 319
320 320 modules_reloading.clear()
321 321 return newm
322 322
323 323 # Save the original hooks
324 324 original_reload = imp.reload
325 325
326 326 # Replacement for reload()
327 327 def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__',
328 328 'numpy', 'numpy._globals')):
329 329 """Recursively reload all modules used in the given module. Optionally
330 330 takes a list of modules to exclude from reloading. The default exclude
331 331 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
332 332 display, exception, and io hooks.
333 333 """
334 334 global found_now
335 335 for i in exclude:
336 336 found_now[i] = 1
337 337 try:
338 338 with replace_import_hook(deep_import_hook):
339 339 return deep_reload_hook(module)
340 340 finally:
341 341 found_now = {}
@@ -1,651 +1,651 b''
1 1 """Various display related classes.
2 2
3 3 Authors : MinRK, gregcaporaso, dannystaple
4 4 """
5 5 from html import escape as html_escape
6 6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 7 from os import walk, sep, fsdecode
8 8
9 9 from IPython.core.display import DisplayObject, TextDisplayObject
10 10
11 11 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
12 12 'FileLink', 'FileLinks', 'Code']
13 13
14 14
15 15 class Audio(DisplayObject):
16 16 """Create an audio object.
17 17
18 18 When this object is returned by an input cell or passed to the
19 19 display function, it will result in Audio controls being displayed
20 20 in the frontend (only works in the notebook).
21 21
22 22 Parameters
23 23 ----------
24 24 data : numpy array, list, unicode, str or bytes
25 25 Can be one of
26 26
27 27 * Numpy 1d array containing the desired waveform (mono)
28 28 * Numpy 2d array containing waveforms for each channel.
29 29 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
30 30 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
31 31 * List of float or integer representing the waveform (mono)
32 32 * String containing the filename
33 33 * Bytestring containing raw PCM data or
34 34 * URL pointing to a file on the web.
35 35
36 36 If the array option is used, the waveform will be normalized.
37 37
38 38 If a filename or url is used, the format support will be browser
39 39 dependent.
40 40 url : unicode
41 41 A URL to download the data from.
42 42 filename : unicode
43 43 Path to a local file to load the data from.
44 44 embed : boolean
45 45 Should the audio data be embedded using a data URI (True) or should
46 46 the original source be referenced. Set this to True if you want the
47 47 audio to playable later with no internet connection in the notebook.
48 48
49 49 Default is `True`, unless the keyword argument `url` is set, then
50 50 default value is `False`.
51 51 rate : integer
52 52 The sampling rate of the raw data.
53 53 Only required when data parameter is being used as an array
54 54 autoplay : bool
55 55 Set to True if the audio should immediately start playing.
56 56 Default is `False`.
57 57 normalize : bool
58 58 Whether audio should be normalized (rescaled) to the maximum possible
59 59 range. Default is `True`. When set to `False`, `data` must be between
60 60 -1 and 1 (inclusive), otherwise an error is raised.
61 61 Applies only when `data` is a list or array of samples; other types of
62 62 audio are never normalized.
63 63
64 64 Examples
65 65 --------
66 66 ::
67 67
68 68 # Generate a sound
69 69 import numpy as np
70 70 framerate = 44100
71 71 t = np.linspace(0,5,framerate*5)
72 72 data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
73 73 Audio(data,rate=framerate)
74 74
75 75 # Can also do stereo or more channels
76 76 dataleft = np.sin(2*np.pi*220*t)
77 77 dataright = np.sin(2*np.pi*224*t)
78 78 Audio([dataleft, dataright],rate=framerate)
79 79
80 80 Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # From URL
81 81 Audio(url="http://www.w3schools.com/html/horse.ogg")
82 82
83 83 Audio('/path/to/sound.wav') # From file
84 84 Audio(filename='/path/to/sound.ogg')
85 85
86 86 Audio(b'RAW_WAV_DATA..) # From bytes
87 87 Audio(data=b'RAW_WAV_DATA..)
88 88
89 89 See Also
90 90 --------
91 91
92 92 See also the ``Audio`` widgets form the ``ipywidget`` package for more flexibility and options.
93 93
94 94 """
95 95 _read_flags = 'rb'
96 96
97 97 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
98 98 element_id=None):
99 99 if filename is None and url is None and data is None:
100 100 raise ValueError("No audio data found. Expecting filename, url, or data.")
101 101 if embed is False and url is None:
102 102 raise ValueError("No url found. Expecting url when embed=False")
103 103
104 104 if url is not None and embed is not True:
105 105 self.embed = False
106 106 else:
107 107 self.embed = True
108 108 self.autoplay = autoplay
109 109 self.element_id = element_id
110 110 super(Audio, self).__init__(data=data, url=url, filename=filename)
111 111
112 112 if self.data is not None and not isinstance(self.data, bytes):
113 113 if rate is None:
114 114 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
115 115 self.data = Audio._make_wav(data, rate, normalize)
116 116
117 117 def reload(self):
118 118 """Reload the raw data from file or URL."""
119 119 import mimetypes
120 120 if self.embed:
121 121 super(Audio, self).reload()
122 122
123 123 if self.filename is not None:
124 124 self.mimetype = mimetypes.guess_type(self.filename)[0]
125 125 elif self.url is not None:
126 126 self.mimetype = mimetypes.guess_type(self.url)[0]
127 127 else:
128 128 self.mimetype = "audio/wav"
129 129
130 130 @staticmethod
131 131 def _make_wav(data, rate, normalize):
132 132 """ Transform a numpy array to a PCM bytestring """
133 133 from io import BytesIO
134 134 import wave
135 135
136 136 try:
137 137 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
138 138 except ImportError:
139 139 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
140 140
141 141 fp = BytesIO()
142 142 waveobj = wave.open(fp,mode='wb')
143 143 waveobj.setnchannels(nchan)
144 144 waveobj.setframerate(rate)
145 145 waveobj.setsampwidth(2)
146 146 waveobj.setcomptype('NONE','NONE')
147 147 waveobj.writeframes(scaled)
148 148 val = fp.getvalue()
149 149 waveobj.close()
150 150
151 151 return val
152 152
153 153 @staticmethod
154 154 def _validate_and_normalize_with_numpy(data, normalize):
155 155 import numpy as np
156 156
157 157 data = np.array(data, dtype=float)
158 158 if len(data.shape) == 1:
159 159 nchan = 1
160 160 elif len(data.shape) == 2:
161 161 # In wave files,channels are interleaved. E.g.,
162 162 # "L1R1L2R2..." for stereo. See
163 163 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
164 164 # for channel ordering
165 165 nchan = data.shape[0]
166 166 data = data.T.ravel()
167 167 else:
168 168 raise ValueError('Array audio input must be a 1D or 2D array')
169 169
170 170 max_abs_value = np.max(np.abs(data))
171 171 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
172 172 scaled = data / normalization_factor * 32767
173 173 return scaled.astype('<h').tostring(), nchan
174 174
175 175
176 176 @staticmethod
177 177 def _validate_and_normalize_without_numpy(data, normalize):
178 178 import array
179 179 import sys
180 180
181 181 data = array.array('f', data)
182 182
183 183 try:
184 184 max_abs_value = float(max([abs(x) for x in data]))
185 except TypeError:
185 except TypeError as e:
186 186 raise TypeError('Only lists of mono audio are '
187 'supported if numpy is not installed')
187 'supported if numpy is not installed') from e
188 188
189 189 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
190 190 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
191 191 if sys.byteorder == 'big':
192 192 scaled.byteswap()
193 193 nchan = 1
194 194 return scaled.tobytes(), nchan
195 195
196 196 @staticmethod
197 197 def _get_normalization_factor(max_abs_value, normalize):
198 198 if not normalize and max_abs_value > 1:
199 199 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
200 200 return max_abs_value if normalize else 1
201 201
202 202 def _data_and_metadata(self):
203 203 """shortcut for returning metadata with url information, if defined"""
204 204 md = {}
205 205 if self.url:
206 206 md['url'] = self.url
207 207 if md:
208 208 return self.data, md
209 209 else:
210 210 return self.data
211 211
212 212 def _repr_html_(self):
213 213 src = """
214 214 <audio {element_id} controls="controls" {autoplay}>
215 215 <source src="{src}" type="{type}" />
216 216 Your browser does not support the audio element.
217 217 </audio>
218 218 """
219 219 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
220 220 element_id=self.element_id_attr())
221 221
222 222 def src_attr(self):
223 223 import base64
224 224 if self.embed and (self.data is not None):
225 225 data = base64=base64.b64encode(self.data).decode('ascii')
226 226 return """data:{type};base64,{base64}""".format(type=self.mimetype,
227 227 base64=data)
228 228 elif self.url is not None:
229 229 return self.url
230 230 else:
231 231 return ""
232 232
233 233 def autoplay_attr(self):
234 234 if(self.autoplay):
235 235 return 'autoplay="autoplay"'
236 236 else:
237 237 return ''
238 238
239 239 def element_id_attr(self):
240 240 if (self.element_id):
241 241 return 'id="{element_id}"'.format(element_id=self.element_id)
242 242 else:
243 243 return ''
244 244
245 245 class IFrame(object):
246 246 """
247 247 Generic class to embed an iframe in an IPython notebook
248 248 """
249 249
250 250 iframe = """
251 251 <iframe
252 252 width="{width}"
253 253 height="{height}"
254 254 src="{src}{params}"
255 255 frameborder="0"
256 256 allowfullscreen
257 257 ></iframe>
258 258 """
259 259
260 260 def __init__(self, src, width, height, **kwargs):
261 261 self.src = src
262 262 self.width = width
263 263 self.height = height
264 264 self.params = kwargs
265 265
266 266 def _repr_html_(self):
267 267 """return the embed iframe"""
268 268 if self.params:
269 269 from urllib.parse import urlencode
270 270 params = "?" + urlencode(self.params)
271 271 else:
272 272 params = ""
273 273 return self.iframe.format(src=self.src,
274 274 width=self.width,
275 275 height=self.height,
276 276 params=params)
277 277
278 278 class YouTubeVideo(IFrame):
279 279 """Class for embedding a YouTube Video in an IPython session, based on its video id.
280 280
281 281 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
282 282 do::
283 283
284 284 vid = YouTubeVideo("foo")
285 285 display(vid)
286 286
287 287 To start from 30 seconds::
288 288
289 289 vid = YouTubeVideo("abc", start=30)
290 290 display(vid)
291 291
292 292 To calculate seconds from time as hours, minutes, seconds use
293 293 :class:`datetime.timedelta`::
294 294
295 295 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
296 296
297 297 Other parameters can be provided as documented at
298 298 https://developers.google.com/youtube/player_parameters#Parameters
299 299
300 300 When converting the notebook using nbconvert, a jpeg representation of the video
301 301 will be inserted in the document.
302 302 """
303 303
304 304 def __init__(self, id, width=400, height=300, **kwargs):
305 305 self.id=id
306 306 src = "https://www.youtube.com/embed/{0}".format(id)
307 307 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
308 308
309 309 def _repr_jpeg_(self):
310 310 # Deferred import
311 311 from urllib.request import urlopen
312 312
313 313 try:
314 314 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
315 315 except IOError:
316 316 return None
317 317
318 318 class VimeoVideo(IFrame):
319 319 """
320 320 Class for embedding a Vimeo video in an IPython session, based on its video id.
321 321 """
322 322
323 323 def __init__(self, id, width=400, height=300, **kwargs):
324 324 src="https://player.vimeo.com/video/{0}".format(id)
325 325 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
326 326
327 327 class ScribdDocument(IFrame):
328 328 """
329 329 Class for embedding a Scribd document in an IPython session
330 330
331 331 Use the start_page params to specify a starting point in the document
332 332 Use the view_mode params to specify display type one off scroll | slideshow | book
333 333
334 334 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
335 335
336 336 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
337 337 """
338 338
339 339 def __init__(self, id, width=400, height=300, **kwargs):
340 340 src="https://www.scribd.com/embeds/{0}/content".format(id)
341 341 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
342 342
343 343 class FileLink(object):
344 344 """Class for embedding a local file link in an IPython session, based on path
345 345
346 346 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
347 347
348 348 you would do::
349 349
350 350 local_file = FileLink("my/data.txt")
351 351 display(local_file)
352 352
353 353 or in the HTML notebook, just::
354 354
355 355 FileLink("my/data.txt")
356 356 """
357 357
358 358 html_link_str = "<a href='%s' target='_blank'>%s</a>"
359 359
360 360 def __init__(self,
361 361 path,
362 362 url_prefix='',
363 363 result_html_prefix='',
364 364 result_html_suffix='<br>'):
365 365 """
366 366 Parameters
367 367 ----------
368 368 path : str
369 369 path to the file or directory that should be formatted
370 370 url_prefix : str
371 371 prefix to be prepended to all files to form a working link [default:
372 372 '']
373 373 result_html_prefix : str
374 374 text to append to beginning to link [default: '']
375 375 result_html_suffix : str
376 376 text to append at the end of link [default: '<br>']
377 377 """
378 378 if isdir(path):
379 379 raise ValueError("Cannot display a directory using FileLink. "
380 380 "Use FileLinks to display '%s'." % path)
381 381 self.path = fsdecode(path)
382 382 self.url_prefix = url_prefix
383 383 self.result_html_prefix = result_html_prefix
384 384 self.result_html_suffix = result_html_suffix
385 385
386 386 def _format_path(self):
387 387 fp = ''.join([self.url_prefix, html_escape(self.path)])
388 388 return ''.join([self.result_html_prefix,
389 389 self.html_link_str % \
390 390 (fp, html_escape(self.path, quote=False)),
391 391 self.result_html_suffix])
392 392
393 393 def _repr_html_(self):
394 394 """return html link to file
395 395 """
396 396 if not exists(self.path):
397 397 return ("Path (<tt>%s</tt>) doesn't exist. "
398 398 "It may still be in the process of "
399 399 "being generated, or you may have the "
400 400 "incorrect path." % self.path)
401 401
402 402 return self._format_path()
403 403
404 404 def __repr__(self):
405 405 """return absolute path to file
406 406 """
407 407 return abspath(self.path)
408 408
409 409 class FileLinks(FileLink):
410 410 """Class for embedding local file links in an IPython session, based on path
411 411
412 412 e.g. to embed links to files that were generated in the IPython notebook
413 413 under ``my/data``, you would do::
414 414
415 415 local_files = FileLinks("my/data")
416 416 display(local_files)
417 417
418 418 or in the HTML notebook, just::
419 419
420 420 FileLinks("my/data")
421 421 """
422 422 def __init__(self,
423 423 path,
424 424 url_prefix='',
425 425 included_suffixes=None,
426 426 result_html_prefix='',
427 427 result_html_suffix='<br>',
428 428 notebook_display_formatter=None,
429 429 terminal_display_formatter=None,
430 430 recursive=True):
431 431 """
432 432 See :class:`FileLink` for the ``path``, ``url_prefix``,
433 433 ``result_html_prefix`` and ``result_html_suffix`` parameters.
434 434
435 435 included_suffixes : list
436 436 Filename suffixes to include when formatting output [default: include
437 437 all files]
438 438
439 439 notebook_display_formatter : function
440 440 Used to format links for display in the notebook. See discussion of
441 441 formatter functions below.
442 442
443 443 terminal_display_formatter : function
444 444 Used to format links for display in the terminal. See discussion of
445 445 formatter functions below.
446 446
447 447 Formatter functions must be of the form::
448 448
449 449 f(dirname, fnames, included_suffixes)
450 450
451 451 dirname : str
452 452 The name of a directory
453 453 fnames : list
454 454 The files in that directory
455 455 included_suffixes : list
456 456 The file suffixes that should be included in the output (passing None
457 457 meansto include all suffixes in the output in the built-in formatters)
458 458 recursive : boolean
459 459 Whether to recurse into subdirectories. Default is True.
460 460
461 461 The function should return a list of lines that will be printed in the
462 462 notebook (if passing notebook_display_formatter) or the terminal (if
463 463 passing terminal_display_formatter). This function is iterated over for
464 464 each directory in self.path. Default formatters are in place, can be
465 465 passed here to support alternative formatting.
466 466
467 467 """
468 468 if isfile(path):
469 469 raise ValueError("Cannot display a file using FileLinks. "
470 470 "Use FileLink to display '%s'." % path)
471 471 self.included_suffixes = included_suffixes
472 472 # remove trailing slashes for more consistent output formatting
473 473 path = path.rstrip('/')
474 474
475 475 self.path = path
476 476 self.url_prefix = url_prefix
477 477 self.result_html_prefix = result_html_prefix
478 478 self.result_html_suffix = result_html_suffix
479 479
480 480 self.notebook_display_formatter = \
481 481 notebook_display_formatter or self._get_notebook_display_formatter()
482 482 self.terminal_display_formatter = \
483 483 terminal_display_formatter or self._get_terminal_display_formatter()
484 484
485 485 self.recursive = recursive
486 486
487 487 def _get_display_formatter(self,
488 488 dirname_output_format,
489 489 fname_output_format,
490 490 fp_format,
491 491 fp_cleaner=None):
492 492 """ generate built-in formatter function
493 493
494 494 this is used to define both the notebook and terminal built-in
495 495 formatters as they only differ by some wrapper text for each entry
496 496
497 497 dirname_output_format: string to use for formatting directory
498 498 names, dirname will be substituted for a single "%s" which
499 499 must appear in this string
500 500 fname_output_format: string to use for formatting file names,
501 501 if a single "%s" appears in the string, fname will be substituted
502 502 if two "%s" appear in the string, the path to fname will be
503 503 substituted for the first and fname will be substituted for the
504 504 second
505 505 fp_format: string to use for formatting filepaths, must contain
506 506 exactly two "%s" and the dirname will be substituted for the first
507 507 and fname will be substituted for the second
508 508 """
509 509 def f(dirname, fnames, included_suffixes=None):
510 510 result = []
511 511 # begin by figuring out which filenames, if any,
512 512 # are going to be displayed
513 513 display_fnames = []
514 514 for fname in fnames:
515 515 if (isfile(join(dirname,fname)) and
516 516 (included_suffixes is None or
517 517 splitext(fname)[1] in included_suffixes)):
518 518 display_fnames.append(fname)
519 519
520 520 if len(display_fnames) == 0:
521 521 # if there are no filenames to display, don't print anything
522 522 # (not even the directory name)
523 523 pass
524 524 else:
525 525 # otherwise print the formatted directory name followed by
526 526 # the formatted filenames
527 527 dirname_output_line = dirname_output_format % dirname
528 528 result.append(dirname_output_line)
529 529 for fname in display_fnames:
530 530 fp = fp_format % (dirname,fname)
531 531 if fp_cleaner is not None:
532 532 fp = fp_cleaner(fp)
533 533 try:
534 534 # output can include both a filepath and a filename...
535 535 fname_output_line = fname_output_format % (fp, fname)
536 536 except TypeError:
537 537 # ... or just a single filepath
538 538 fname_output_line = fname_output_format % fname
539 539 result.append(fname_output_line)
540 540 return result
541 541 return f
542 542
543 543 def _get_notebook_display_formatter(self,
544 544 spacer="&nbsp;&nbsp;"):
545 545 """ generate function to use for notebook formatting
546 546 """
547 547 dirname_output_format = \
548 548 self.result_html_prefix + "%s/" + self.result_html_suffix
549 549 fname_output_format = \
550 550 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
551 551 fp_format = self.url_prefix + '%s/%s'
552 552 if sep == "\\":
553 553 # Working on a platform where the path separator is "\", so
554 554 # must convert these to "/" for generating a URI
555 555 def fp_cleaner(fp):
556 556 # Replace all occurrences of backslash ("\") with a forward
557 557 # slash ("/") - this is necessary on windows when a path is
558 558 # provided as input, but we must link to a URI
559 559 return fp.replace('\\','/')
560 560 else:
561 561 fp_cleaner = None
562 562
563 563 return self._get_display_formatter(dirname_output_format,
564 564 fname_output_format,
565 565 fp_format,
566 566 fp_cleaner)
567 567
568 568 def _get_terminal_display_formatter(self,
569 569 spacer=" "):
570 570 """ generate function to use for terminal formatting
571 571 """
572 572 dirname_output_format = "%s/"
573 573 fname_output_format = spacer + "%s"
574 574 fp_format = '%s/%s'
575 575
576 576 return self._get_display_formatter(dirname_output_format,
577 577 fname_output_format,
578 578 fp_format)
579 579
580 580 def _format_path(self):
581 581 result_lines = []
582 582 if self.recursive:
583 583 walked_dir = list(walk(self.path))
584 584 else:
585 585 walked_dir = [next(walk(self.path))]
586 586 walked_dir.sort()
587 587 for dirname, subdirs, fnames in walked_dir:
588 588 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
589 589 return '\n'.join(result_lines)
590 590
591 591 def __repr__(self):
592 592 """return newline-separated absolute paths
593 593 """
594 594 result_lines = []
595 595 if self.recursive:
596 596 walked_dir = list(walk(self.path))
597 597 else:
598 598 walked_dir = [next(walk(self.path))]
599 599 walked_dir.sort()
600 600 for dirname, subdirs, fnames in walked_dir:
601 601 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
602 602 return '\n'.join(result_lines)
603 603
604 604
605 605 class Code(TextDisplayObject):
606 606 """Display syntax-highlighted source code.
607 607
608 608 This uses Pygments to highlight the code for HTML and Latex output.
609 609
610 610 Parameters
611 611 ----------
612 612 data : str
613 613 The code as a string
614 614 url : str
615 615 A URL to fetch the code from
616 616 filename : str
617 617 A local filename to load the code from
618 618 language : str
619 619 The short name of a Pygments lexer to use for highlighting.
620 620 If not specified, it will guess the lexer based on the filename
621 621 or the code. Available lexers: http://pygments.org/docs/lexers/
622 622 """
623 623 def __init__(self, data=None, url=None, filename=None, language=None):
624 624 self.language = language
625 625 super().__init__(data=data, url=url, filename=filename)
626 626
627 627 def _get_lexer(self):
628 628 if self.language:
629 629 from pygments.lexers import get_lexer_by_name
630 630 return get_lexer_by_name(self.language)
631 631 elif self.filename:
632 632 from pygments.lexers import get_lexer_for_filename
633 633 return get_lexer_for_filename(self.filename)
634 634 else:
635 635 from pygments.lexers import guess_lexer
636 636 return guess_lexer(self.data)
637 637
638 638 def __repr__(self):
639 639 return self.data
640 640
641 641 def _repr_html_(self):
642 642 from pygments import highlight
643 643 from pygments.formatters import HtmlFormatter
644 644 fmt = HtmlFormatter()
645 645 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
646 646 return style + highlight(self.data, self._get_lexer(), fmt)
647 647
648 648 def _repr_latex_(self):
649 649 from pygments import highlight
650 650 from pygments.formatters import LatexFormatter
651 651 return highlight(self.data, self._get_lexer(), LatexFormatter())
@@ -1,663 +1,663 b''
1 1 # coding: utf-8
2 2 """
3 3 Deprecated since IPython 5.0
4 4
5 5 Inputhook management for GUI event loop integration.
6 6 """
7 7
8 8 # Copyright (c) IPython Development Team.
9 9 # Distributed under the terms of the Modified BSD License.
10 10
11 11 try:
12 12 import ctypes
13 13 except ImportError:
14 14 ctypes = None
15 15 except SystemError: # IronPython issue, 2/8/2014
16 16 ctypes = None
17 17 import os
18 18 import platform
19 19 import sys
20 20 from distutils.version import LooseVersion as V
21 21
22 22 from warnings import warn
23 23
24 24
25 25 warn("`IPython.lib.inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
26 26 DeprecationWarning, stacklevel=2)
27 27
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Constants
31 31 #-----------------------------------------------------------------------------
32 32
33 33 # Constants for identifying the GUI toolkits.
34 34 GUI_WX = 'wx'
35 35 GUI_QT = 'qt'
36 36 GUI_QT4 = 'qt4'
37 37 GUI_GTK = 'gtk'
38 38 GUI_TK = 'tk'
39 39 GUI_OSX = 'osx'
40 40 GUI_GLUT = 'glut'
41 41 GUI_PYGLET = 'pyglet'
42 42 GUI_GTK3 = 'gtk3'
43 43 GUI_NONE = 'none' # i.e. disable
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Utilities
47 47 #-----------------------------------------------------------------------------
48 48
49 49 def _stdin_ready_posix():
50 50 """Return True if there's something to read on stdin (posix version)."""
51 51 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
52 52 return bool(infds)
53 53
54 54 def _stdin_ready_nt():
55 55 """Return True if there's something to read on stdin (nt version)."""
56 56 return msvcrt.kbhit()
57 57
58 58 def _stdin_ready_other():
59 59 """Return True, assuming there's something to read on stdin."""
60 60 return True
61 61
62 62 def _use_appnope():
63 63 """Should we use appnope for dealing with OS X app nap?
64 64
65 65 Checks if we are on OS X 10.9 or greater.
66 66 """
67 67 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
68 68
69 69 def _ignore_CTRL_C_posix():
70 70 """Ignore CTRL+C (SIGINT)."""
71 71 signal.signal(signal.SIGINT, signal.SIG_IGN)
72 72
73 73 def _allow_CTRL_C_posix():
74 74 """Take CTRL+C into account (SIGINT)."""
75 75 signal.signal(signal.SIGINT, signal.default_int_handler)
76 76
77 77 def _ignore_CTRL_C_other():
78 78 """Ignore CTRL+C (not implemented)."""
79 79 pass
80 80
81 81 def _allow_CTRL_C_other():
82 82 """Take CTRL+C into account (not implemented)."""
83 83 pass
84 84
85 85 if os.name == 'posix':
86 86 import select
87 87 import signal
88 88 stdin_ready = _stdin_ready_posix
89 89 ignore_CTRL_C = _ignore_CTRL_C_posix
90 90 allow_CTRL_C = _allow_CTRL_C_posix
91 91 elif os.name == 'nt':
92 92 import msvcrt
93 93 stdin_ready = _stdin_ready_nt
94 94 ignore_CTRL_C = _ignore_CTRL_C_other
95 95 allow_CTRL_C = _allow_CTRL_C_other
96 96 else:
97 97 stdin_ready = _stdin_ready_other
98 98 ignore_CTRL_C = _ignore_CTRL_C_other
99 99 allow_CTRL_C = _allow_CTRL_C_other
100 100
101 101
102 102 #-----------------------------------------------------------------------------
103 103 # Main InputHookManager class
104 104 #-----------------------------------------------------------------------------
105 105
106 106
107 107 class InputHookManager(object):
108 108 """DEPRECATED since IPython 5.0
109 109
110 110 Manage PyOS_InputHook for different GUI toolkits.
111 111
112 112 This class installs various hooks under ``PyOSInputHook`` to handle
113 113 GUI event loop integration.
114 114 """
115 115
116 116 def __init__(self):
117 117 if ctypes is None:
118 118 warn("IPython GUI event loop requires ctypes, %gui will not be available")
119 119 else:
120 120 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
121 121 self.guihooks = {}
122 122 self.aliases = {}
123 123 self.apps = {}
124 124 self._reset()
125 125
126 126 def _reset(self):
127 127 self._callback_pyfunctype = None
128 128 self._callback = None
129 129 self._installed = False
130 130 self._current_gui = None
131 131
132 132 def get_pyos_inputhook(self):
133 133 """DEPRECATED since IPython 5.0
134 134
135 135 Return the current PyOS_InputHook as a ctypes.c_void_p."""
136 136 warn("`get_pyos_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
137 137 DeprecationWarning, stacklevel=2)
138 138 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
139 139
140 140 def get_pyos_inputhook_as_func(self):
141 141 """DEPRECATED since IPython 5.0
142 142
143 143 Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
144 144 warn("`get_pyos_inputhook_as_func` is deprecated since IPython 5.0 and will be removed in future versions.",
145 145 DeprecationWarning, stacklevel=2)
146 146 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
147 147
148 148 def set_inputhook(self, callback):
149 149 """DEPRECATED since IPython 5.0
150 150
151 151 Set PyOS_InputHook to callback and return the previous one."""
152 152 # On platforms with 'readline' support, it's all too likely to
153 153 # have a KeyboardInterrupt signal delivered *even before* an
154 154 # initial ``try:`` clause in the callback can be executed, so
155 155 # we need to disable CTRL+C in this situation.
156 156 ignore_CTRL_C()
157 157 self._callback = callback
158 158 self._callback_pyfunctype = self.PYFUNC(callback)
159 159 pyos_inputhook_ptr = self.get_pyos_inputhook()
160 160 original = self.get_pyos_inputhook_as_func()
161 161 pyos_inputhook_ptr.value = \
162 162 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
163 163 self._installed = True
164 164 return original
165 165
166 166 def clear_inputhook(self, app=None):
167 167 """DEPRECATED since IPython 5.0
168 168
169 169 Set PyOS_InputHook to NULL and return the previous one.
170 170
171 171 Parameters
172 172 ----------
173 173 app : optional, ignored
174 174 This parameter is allowed only so that clear_inputhook() can be
175 175 called with a similar interface as all the ``enable_*`` methods. But
176 176 the actual value of the parameter is ignored. This uniform interface
177 177 makes it easier to have user-level entry points in the main IPython
178 178 app like :meth:`enable_gui`."""
179 179 warn("`clear_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.",
180 180 DeprecationWarning, stacklevel=2)
181 181 pyos_inputhook_ptr = self.get_pyos_inputhook()
182 182 original = self.get_pyos_inputhook_as_func()
183 183 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
184 184 allow_CTRL_C()
185 185 self._reset()
186 186 return original
187 187
188 188 def clear_app_refs(self, gui=None):
189 189 """DEPRECATED since IPython 5.0
190 190
191 191 Clear IPython's internal reference to an application instance.
192 192
193 193 Whenever we create an app for a user on qt4 or wx, we hold a
194 194 reference to the app. This is needed because in some cases bad things
195 195 can happen if a user doesn't hold a reference themselves. This
196 196 method is provided to clear the references we are holding.
197 197
198 198 Parameters
199 199 ----------
200 200 gui : None or str
201 201 If None, clear all app references. If ('wx', 'qt4') clear
202 202 the app for that toolkit. References are not held for gtk or tk
203 203 as those toolkits don't have the notion of an app.
204 204 """
205 205 warn("`clear_app_refs` is deprecated since IPython 5.0 and will be removed in future versions.",
206 206 DeprecationWarning, stacklevel=2)
207 207 if gui is None:
208 208 self.apps = {}
209 209 elif gui in self.apps:
210 210 del self.apps[gui]
211 211
212 212 def register(self, toolkitname, *aliases):
213 213 """DEPRECATED since IPython 5.0
214 214
215 215 Register a class to provide the event loop for a given GUI.
216 216
217 217 This is intended to be used as a class decorator. It should be passed
218 218 the names with which to register this GUI integration. The classes
219 219 themselves should subclass :class:`InputHookBase`.
220 220
221 221 ::
222 222
223 223 @inputhook_manager.register('qt')
224 224 class QtInputHook(InputHookBase):
225 225 def enable(self, app=None):
226 226 ...
227 227 """
228 228 warn("`register` is deprecated since IPython 5.0 and will be removed in future versions.",
229 229 DeprecationWarning, stacklevel=2)
230 230 def decorator(cls):
231 231 if ctypes is not None:
232 232 inst = cls(self)
233 233 self.guihooks[toolkitname] = inst
234 234 for a in aliases:
235 235 self.aliases[a] = toolkitname
236 236 return cls
237 237 return decorator
238 238
239 239 def current_gui(self):
240 240 """DEPRECATED since IPython 5.0
241 241
242 242 Return a string indicating the currently active GUI or None."""
243 243 warn("`current_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
244 244 DeprecationWarning, stacklevel=2)
245 245 return self._current_gui
246 246
247 247 def enable_gui(self, gui=None, app=None):
248 248 """DEPRECATED since IPython 5.0
249 249
250 250 Switch amongst GUI input hooks by name.
251 251
252 252 This is a higher level method than :meth:`set_inputhook` - it uses the
253 253 GUI name to look up a registered object which enables the input hook
254 254 for that GUI.
255 255
256 256 Parameters
257 257 ----------
258 258 gui : optional, string or None
259 259 If None (or 'none'), clears input hook, otherwise it must be one
260 260 of the recognized GUI names (see ``GUI_*`` constants in module).
261 261
262 262 app : optional, existing application object.
263 263 For toolkits that have the concept of a global app, you can supply an
264 264 existing one. If not given, the toolkit will be probed for one, and if
265 265 none is found, a new one will be created. Note that GTK does not have
266 266 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
267 267
268 268 Returns
269 269 -------
270 270 The output of the underlying gui switch routine, typically the actual
271 271 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
272 272 one.
273 273 """
274 274 warn("`enable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
275 275 DeprecationWarning, stacklevel=2)
276 276 if gui in (None, GUI_NONE):
277 277 return self.disable_gui()
278 278
279 279 if gui in self.aliases:
280 280 return self.enable_gui(self.aliases[gui], app)
281 281
282 282 try:
283 283 gui_hook = self.guihooks[gui]
284 except KeyError:
284 except KeyError as e:
285 285 e = "Invalid GUI request {!r}, valid ones are: {}"
286 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
286 raise ValueError(e.format(gui, ', '.join(self.guihooks))) from e
287 287 self._current_gui = gui
288 288
289 289 app = gui_hook.enable(app)
290 290 if app is not None:
291 291 app._in_event_loop = True
292 292 self.apps[gui] = app
293 293 return app
294 294
295 295 def disable_gui(self):
296 296 """DEPRECATED since IPython 5.0
297 297
298 298 Disable GUI event loop integration.
299 299
300 300 If an application was registered, this sets its ``_in_event_loop``
301 301 attribute to False. It then calls :meth:`clear_inputhook`.
302 302 """
303 303 warn("`disable_gui` is deprecated since IPython 5.0 and will be removed in future versions.",
304 304 DeprecationWarning, stacklevel=2)
305 305 gui = self._current_gui
306 306 if gui in self.apps:
307 307 self.apps[gui]._in_event_loop = False
308 308 return self.clear_inputhook()
309 309
310 310 class InputHookBase(object):
311 311 """DEPRECATED since IPython 5.0
312 312
313 313 Base class for input hooks for specific toolkits.
314 314
315 315 Subclasses should define an :meth:`enable` method with one argument, ``app``,
316 316 which will either be an instance of the toolkit's application class, or None.
317 317 They may also define a :meth:`disable` method with no arguments.
318 318 """
319 319 def __init__(self, manager):
320 320 self.manager = manager
321 321
322 322 def disable(self):
323 323 pass
324 324
325 325 inputhook_manager = InputHookManager()
326 326
327 327 @inputhook_manager.register('osx')
328 328 class NullInputHook(InputHookBase):
329 329 """DEPRECATED since IPython 5.0
330 330
331 331 A null inputhook that doesn't need to do anything"""
332 332 def enable(self, app=None):
333 333 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
334 334 DeprecationWarning, stacklevel=2)
335 335
336 336 @inputhook_manager.register('wx')
337 337 class WxInputHook(InputHookBase):
338 338 def enable(self, app=None):
339 339 """DEPRECATED since IPython 5.0
340 340
341 341 Enable event loop integration with wxPython.
342 342
343 343 Parameters
344 344 ----------
345 345 app : WX Application, optional.
346 346 Running application to use. If not given, we probe WX for an
347 347 existing application object, and create a new one if none is found.
348 348
349 349 Notes
350 350 -----
351 351 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
352 352 the wxPython to integrate with terminal based applications like
353 353 IPython.
354 354
355 355 If ``app`` is not given we probe for an existing one, and return it if
356 356 found. If no existing app is found, we create an :class:`wx.App` as
357 357 follows::
358 358
359 359 import wx
360 360 app = wx.App(redirect=False, clearSigInt=False)
361 361 """
362 362 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
363 363 DeprecationWarning, stacklevel=2)
364 364 import wx
365 365
366 366 wx_version = V(wx.__version__).version
367 367
368 368 if wx_version < [2, 8]:
369 369 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
370 370
371 371 from IPython.lib.inputhookwx import inputhook_wx
372 372 self.manager.set_inputhook(inputhook_wx)
373 373 if _use_appnope():
374 374 from appnope import nope
375 375 nope()
376 376
377 377 import wx
378 378 if app is None:
379 379 app = wx.GetApp()
380 380 if app is None:
381 381 app = wx.App(redirect=False, clearSigInt=False)
382 382
383 383 return app
384 384
385 385 def disable(self):
386 386 """DEPRECATED since IPython 5.0
387 387
388 388 Disable event loop integration with wxPython.
389 389
390 390 This restores appnapp on OS X
391 391 """
392 392 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
393 393 DeprecationWarning, stacklevel=2)
394 394 if _use_appnope():
395 395 from appnope import nap
396 396 nap()
397 397
398 398 @inputhook_manager.register('qt', 'qt4')
399 399 class Qt4InputHook(InputHookBase):
400 400 def enable(self, app=None):
401 401 """DEPRECATED since IPython 5.0
402 402
403 403 Enable event loop integration with PyQt4.
404 404
405 405 Parameters
406 406 ----------
407 407 app : Qt Application, optional.
408 408 Running application to use. If not given, we probe Qt for an
409 409 existing application object, and create a new one if none is found.
410 410
411 411 Notes
412 412 -----
413 413 This methods sets the PyOS_InputHook for PyQt4, which allows
414 414 the PyQt4 to integrate with terminal based applications like
415 415 IPython.
416 416
417 417 If ``app`` is not given we probe for an existing one, and return it if
418 418 found. If no existing app is found, we create an :class:`QApplication`
419 419 as follows::
420 420
421 421 from PyQt4 import QtCore
422 422 app = QtGui.QApplication(sys.argv)
423 423 """
424 424 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
425 425 DeprecationWarning, stacklevel=2)
426 426 from IPython.lib.inputhookqt4 import create_inputhook_qt4
427 427 app, inputhook_qt4 = create_inputhook_qt4(self.manager, app)
428 428 self.manager.set_inputhook(inputhook_qt4)
429 429 if _use_appnope():
430 430 from appnope import nope
431 431 nope()
432 432
433 433 return app
434 434
435 435 def disable_qt4(self):
436 436 """DEPRECATED since IPython 5.0
437 437
438 438 Disable event loop integration with PyQt4.
439 439
440 440 This restores appnapp on OS X
441 441 """
442 442 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
443 443 DeprecationWarning, stacklevel=2)
444 444 if _use_appnope():
445 445 from appnope import nap
446 446 nap()
447 447
448 448
449 449 @inputhook_manager.register('qt5')
450 450 class Qt5InputHook(Qt4InputHook):
451 451 def enable(self, app=None):
452 452 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
453 453 DeprecationWarning, stacklevel=2)
454 454 os.environ['QT_API'] = 'pyqt5'
455 455 return Qt4InputHook.enable(self, app)
456 456
457 457
458 458 @inputhook_manager.register('gtk')
459 459 class GtkInputHook(InputHookBase):
460 460 def enable(self, app=None):
461 461 """DEPRECATED since IPython 5.0
462 462
463 463 Enable event loop integration with PyGTK.
464 464
465 465 Parameters
466 466 ----------
467 467 app : ignored
468 468 Ignored, it's only a placeholder to keep the call signature of all
469 469 gui activation methods consistent, which simplifies the logic of
470 470 supporting magics.
471 471
472 472 Notes
473 473 -----
474 474 This methods sets the PyOS_InputHook for PyGTK, which allows
475 475 the PyGTK to integrate with terminal based applications like
476 476 IPython.
477 477 """
478 478 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
479 479 DeprecationWarning, stacklevel=2)
480 480 import gtk
481 481 try:
482 482 gtk.set_interactive(True)
483 483 except AttributeError:
484 484 # For older versions of gtk, use our own ctypes version
485 485 from IPython.lib.inputhookgtk import inputhook_gtk
486 486 self.manager.set_inputhook(inputhook_gtk)
487 487
488 488
489 489 @inputhook_manager.register('tk')
490 490 class TkInputHook(InputHookBase):
491 491 def enable(self, app=None):
492 492 """DEPRECATED since IPython 5.0
493 493
494 494 Enable event loop integration with Tk.
495 495
496 496 Parameters
497 497 ----------
498 498 app : toplevel :class:`Tkinter.Tk` widget, optional.
499 499 Running toplevel widget to use. If not given, we probe Tk for an
500 500 existing one, and create a new one if none is found.
501 501
502 502 Notes
503 503 -----
504 504 If you have already created a :class:`Tkinter.Tk` object, the only
505 505 thing done by this method is to register with the
506 506 :class:`InputHookManager`, since creating that object automatically
507 507 sets ``PyOS_InputHook``.
508 508 """
509 509 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
510 510 DeprecationWarning, stacklevel=2)
511 511 if app is None:
512 512 from tkinter import Tk
513 513 app = Tk()
514 514 app.withdraw()
515 515 self.manager.apps[GUI_TK] = app
516 516 return app
517 517
518 518
519 519 @inputhook_manager.register('glut')
520 520 class GlutInputHook(InputHookBase):
521 521 def enable(self, app=None):
522 522 """DEPRECATED since IPython 5.0
523 523
524 524 Enable event loop integration with GLUT.
525 525
526 526 Parameters
527 527 ----------
528 528
529 529 app : ignored
530 530 Ignored, it's only a placeholder to keep the call signature of all
531 531 gui activation methods consistent, which simplifies the logic of
532 532 supporting magics.
533 533
534 534 Notes
535 535 -----
536 536
537 537 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
538 538 integrate with terminal based applications like IPython. Due to GLUT
539 539 limitations, it is currently not possible to start the event loop
540 540 without first creating a window. You should thus not create another
541 541 window but use instead the created one. See 'gui-glut.py' in the
542 542 docs/examples/lib directory.
543 543
544 544 The default screen mode is set to:
545 545 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
546 546 """
547 547 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
548 548 DeprecationWarning, stacklevel=2)
549 549
550 550 import OpenGL.GLUT as glut
551 551 from IPython.lib.inputhookglut import glut_display_mode, \
552 552 glut_close, glut_display, \
553 553 glut_idle, inputhook_glut
554 554
555 555 if GUI_GLUT not in self.manager.apps:
556 556 glut.glutInit( sys.argv )
557 557 glut.glutInitDisplayMode( glut_display_mode )
558 558 # This is specific to freeglut
559 559 if bool(glut.glutSetOption):
560 560 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
561 561 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
562 562 glut.glutCreateWindow( sys.argv[0] )
563 563 glut.glutReshapeWindow( 1, 1 )
564 564 glut.glutHideWindow( )
565 565 glut.glutWMCloseFunc( glut_close )
566 566 glut.glutDisplayFunc( glut_display )
567 567 glut.glutIdleFunc( glut_idle )
568 568 else:
569 569 glut.glutWMCloseFunc( glut_close )
570 570 glut.glutDisplayFunc( glut_display )
571 571 glut.glutIdleFunc( glut_idle)
572 572 self.manager.set_inputhook( inputhook_glut )
573 573
574 574
575 575 def disable(self):
576 576 """DEPRECATED since IPython 5.0
577 577
578 578 Disable event loop integration with glut.
579 579
580 580 This sets PyOS_InputHook to NULL and set the display function to a
581 581 dummy one and set the timer to a dummy timer that will be triggered
582 582 very far in the future.
583 583 """
584 584 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
585 585 DeprecationWarning, stacklevel=2)
586 586 import OpenGL.GLUT as glut
587 587 from glut_support import glutMainLoopEvent
588 588
589 589 glut.glutHideWindow() # This is an event to be processed below
590 590 glutMainLoopEvent()
591 591 super(GlutInputHook, self).disable()
592 592
593 593 @inputhook_manager.register('pyglet')
594 594 class PygletInputHook(InputHookBase):
595 595 def enable(self, app=None):
596 596 """DEPRECATED since IPython 5.0
597 597
598 598 Enable event loop integration with pyglet.
599 599
600 600 Parameters
601 601 ----------
602 602 app : ignored
603 603 Ignored, it's only a placeholder to keep the call signature of all
604 604 gui activation methods consistent, which simplifies the logic of
605 605 supporting magics.
606 606
607 607 Notes
608 608 -----
609 609 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
610 610 pyglet to integrate with terminal based applications like
611 611 IPython.
612 612
613 613 """
614 614 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
615 615 DeprecationWarning, stacklevel=2)
616 616 from IPython.lib.inputhookpyglet import inputhook_pyglet
617 617 self.manager.set_inputhook(inputhook_pyglet)
618 618 return app
619 619
620 620
621 621 @inputhook_manager.register('gtk3')
622 622 class Gtk3InputHook(InputHookBase):
623 623 def enable(self, app=None):
624 624 """DEPRECATED since IPython 5.0
625 625
626 626 Enable event loop integration with Gtk3 (gir bindings).
627 627
628 628 Parameters
629 629 ----------
630 630 app : ignored
631 631 Ignored, it's only a placeholder to keep the call signature of all
632 632 gui activation methods consistent, which simplifies the logic of
633 633 supporting magics.
634 634
635 635 Notes
636 636 -----
637 637 This methods sets the PyOS_InputHook for Gtk3, which allows
638 638 the Gtk3 to integrate with terminal based applications like
639 639 IPython.
640 640 """
641 641 warn("This function is deprecated since IPython 5.0 and will be removed in future versions.",
642 642 DeprecationWarning, stacklevel=2)
643 643 from IPython.lib.inputhookgtk3 import inputhook_gtk3
644 644 self.manager.set_inputhook(inputhook_gtk3)
645 645
646 646
647 647 clear_inputhook = inputhook_manager.clear_inputhook
648 648 set_inputhook = inputhook_manager.set_inputhook
649 649 current_gui = inputhook_manager.current_gui
650 650 clear_app_refs = inputhook_manager.clear_app_refs
651 651 enable_gui = inputhook_manager.enable_gui
652 652 disable_gui = inputhook_manager.disable_gui
653 653 register = inputhook_manager.register
654 654 guis = inputhook_manager.guihooks
655 655
656 656
657 657 def _deprecated_disable():
658 658 warn("This function is deprecated since IPython 4.0 use disable_gui() instead",
659 659 DeprecationWarning, stacklevel=2)
660 660 inputhook_manager.disable_gui()
661 661
662 662 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
663 663 disable_pyglet = disable_osx = _deprecated_disable
@@ -1,172 +1,172 b''
1 1 # coding: utf-8
2 2 """
3 3 GLUT Inputhook support functions
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 # GLUT is quite an old library and it is difficult to ensure proper
14 14 # integration within IPython since original GLUT does not allow to handle
15 15 # events one by one. Instead, it requires for the mainloop to be entered
16 16 # and never returned (there is not even a function to exit he
17 17 # mainloop). Fortunately, there are alternatives such as freeglut
18 18 # (available for linux and windows) and the OSX implementation gives
19 19 # access to a glutCheckLoop() function that blocks itself until a new
20 20 # event is received. This means we have to setup the idle callback to
21 21 # ensure we got at least one event that will unblock the function.
22 22 #
23 23 # Furthermore, it is not possible to install these handlers without a window
24 24 # being first created. We choose to make this window invisible. This means that
25 25 # display mode options are set at this level and user won't be able to change
26 26 # them later without modifying the code. This should probably be made available
27 27 # via IPython options system.
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Imports
31 31 #-----------------------------------------------------------------------------
32 32 import os
33 33 import sys
34 34 import time
35 35 import signal
36 36 import OpenGL.GLUT as glut
37 37 import OpenGL.platform as platform
38 38 from timeit import default_timer as clock
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Constants
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Frame per second : 60
45 45 # Should probably be an IPython option
46 46 glut_fps = 60
47 47
48 48
49 49 # Display mode : double buffeed + rgba + depth
50 50 # Should probably be an IPython option
51 51 glut_display_mode = (glut.GLUT_DOUBLE |
52 52 glut.GLUT_RGBA |
53 53 glut.GLUT_DEPTH)
54 54
55 55 glutMainLoopEvent = None
56 56 if sys.platform == 'darwin':
57 57 try:
58 58 glutCheckLoop = platform.createBaseFunction(
59 59 'glutCheckLoop', dll=platform.GLUT, resultType=None,
60 60 argTypes=[],
61 61 doc='glutCheckLoop( ) -> None',
62 62 argNames=(),
63 63 )
64 except AttributeError:
64 except AttributeError as e:
65 65 raise RuntimeError(
66 66 '''Your glut implementation does not allow interactive sessions. '''
67 '''Consider installing freeglut.''')
67 '''Consider installing freeglut.''') from e
68 68 glutMainLoopEvent = glutCheckLoop
69 69 elif glut.HAVE_FREEGLUT:
70 70 glutMainLoopEvent = glut.glutMainLoopEvent
71 71 else:
72 72 raise RuntimeError(
73 73 '''Your glut implementation does not allow interactive sessions. '''
74 74 '''Consider installing freeglut.''')
75 75
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # Platform-dependent imports and functions
79 79 #-----------------------------------------------------------------------------
80 80
81 81 if os.name == 'posix':
82 82 import select
83 83
84 84 def stdin_ready():
85 85 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
86 86 if infds:
87 87 return True
88 88 else:
89 89 return False
90 90
91 91 elif sys.platform == 'win32':
92 92 import msvcrt
93 93
94 94 def stdin_ready():
95 95 return msvcrt.kbhit()
96 96
97 97 #-----------------------------------------------------------------------------
98 98 # Callback functions
99 99 #-----------------------------------------------------------------------------
100 100
101 101 def glut_display():
102 102 # Dummy display function
103 103 pass
104 104
105 105 def glut_idle():
106 106 # Dummy idle function
107 107 pass
108 108
109 109 def glut_close():
110 110 # Close function only hides the current window
111 111 glut.glutHideWindow()
112 112 glutMainLoopEvent()
113 113
114 114 def glut_int_handler(signum, frame):
115 115 # Catch sigint and print the default message
116 116 signal.signal(signal.SIGINT, signal.default_int_handler)
117 117 print('\nKeyboardInterrupt')
118 118 # Need to reprint the prompt at this stage
119 119
120 120
121 121
122 122 #-----------------------------------------------------------------------------
123 123 # Code
124 124 #-----------------------------------------------------------------------------
125 125 def inputhook_glut():
126 126 """Run the pyglet event loop by processing pending events only.
127 127
128 128 This keeps processing pending events until stdin is ready. After
129 129 processing all pending events, a call to time.sleep is inserted. This is
130 130 needed, otherwise, CPU usage is at 100%. This sleep time should be tuned
131 131 though for best performance.
132 132 """
133 133 # We need to protect against a user pressing Control-C when IPython is
134 134 # idle and this is running. We trap KeyboardInterrupt and pass.
135 135
136 136 signal.signal(signal.SIGINT, glut_int_handler)
137 137
138 138 try:
139 139 t = clock()
140 140
141 141 # Make sure the default window is set after a window has been closed
142 142 if glut.glutGetWindow() == 0:
143 143 glut.glutSetWindow( 1 )
144 144 glutMainLoopEvent()
145 145 return 0
146 146
147 147 while not stdin_ready():
148 148 glutMainLoopEvent()
149 149 # We need to sleep at this point to keep the idle CPU load
150 150 # low. However, if sleep to long, GUI response is poor. As
151 151 # a compromise, we watch how often GUI events are being processed
152 152 # and switch between a short and long sleep time. Here are some
153 153 # stats useful in helping to tune this.
154 154 # time CPU load
155 155 # 0.001 13%
156 156 # 0.005 3%
157 157 # 0.01 1.5%
158 158 # 0.05 0.5%
159 159 used_time = clock() - t
160 160 if used_time > 10.0:
161 161 # print 'Sleep for 1 s' # dbg
162 162 time.sleep(1.0)
163 163 elif used_time > 0.1:
164 164 # Few GUI events coming in, so we can sleep longer
165 165 # print 'Sleep for 0.05 s' # dbg
166 166 time.sleep(0.05)
167 167 else:
168 168 # Many GUI events coming in, so sleep only very little
169 169 time.sleep(0.001)
170 170 except KeyboardInterrupt:
171 171 pass
172 172 return 0
@@ -1,220 +1,220 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for handling LaTeX."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO, open
8 8 import os
9 9 import tempfile
10 10 import shutil
11 11 import subprocess
12 12 from base64 import encodebytes
13 13 import textwrap
14 14
15 15 from IPython.utils.process import find_cmd, FindCmdError
16 16 from traitlets.config import get_config
17 17 from traitlets.config.configurable import SingletonConfigurable
18 18 from traitlets import List, Bool, Unicode
19 19 from IPython.utils.py3compat import cast_unicode
20 20
21 21
22 22 class LaTeXTool(SingletonConfigurable):
23 23 """An object to store configuration of the LaTeX tool."""
24 24 def _config_default(self):
25 25 return get_config()
26 26
27 27 backends = List(
28 28 Unicode(), ["matplotlib", "dvipng"],
29 29 help="Preferred backend to draw LaTeX math equations. "
30 30 "Backends in the list are checked one by one and the first "
31 31 "usable one is used. Note that `matplotlib` backend "
32 32 "is usable only for inline style equations. To draw "
33 33 "display style equations, `dvipng` backend must be specified. ",
34 34 # It is a List instead of Enum, to make configuration more
35 35 # flexible. For example, to use matplotlib mainly but dvipng
36 36 # for display style, the default ["matplotlib", "dvipng"] can
37 37 # be used. To NOT use dvipng so that other repr such as
38 38 # unicode pretty printing is used, you can use ["matplotlib"].
39 39 ).tag(config=True)
40 40
41 41 use_breqn = Bool(
42 42 True,
43 43 help="Use breqn.sty to automatically break long equations. "
44 44 "This configuration takes effect only for dvipng backend.",
45 45 ).tag(config=True)
46 46
47 47 packages = List(
48 48 ['amsmath', 'amsthm', 'amssymb', 'bm'],
49 49 help="A list of packages to use for dvipng backend. "
50 50 "'breqn' will be automatically appended when use_breqn=True.",
51 51 ).tag(config=True)
52 52
53 53 preamble = Unicode(
54 54 help="Additional preamble to use when generating LaTeX source "
55 55 "for dvipng backend.",
56 56 ).tag(config=True)
57 57
58 58
59 59 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
60 60 scale=1.0):
61 61 """Render a LaTeX string to PNG.
62 62
63 63 Parameters
64 64 ----------
65 65 s : str
66 66 The raw string containing valid inline LaTeX.
67 67 encode : bool, optional
68 68 Should the PNG data base64 encoded to make it JSON'able.
69 69 backend : {matplotlib, dvipng}
70 70 Backend for producing PNG data.
71 71 wrap : bool
72 72 If true, Automatically wrap `s` as a LaTeX equation.
73 73 color : string
74 74 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
75 75 format, e.g. '#AA20FA'.
76 76 scale : float
77 77 Scale factor for the resulting PNG.
78 78
79 79 None is returned when the backend cannot be used.
80 80
81 81 """
82 82 s = cast_unicode(s)
83 83 allowed_backends = LaTeXTool.instance().backends
84 84 if backend is None:
85 85 backend = allowed_backends[0]
86 86 if backend not in allowed_backends:
87 87 return None
88 88 if backend == 'matplotlib':
89 89 f = latex_to_png_mpl
90 90 elif backend == 'dvipng':
91 91 f = latex_to_png_dvipng
92 92 if color.startswith('#'):
93 93 # Convert hex RGB color to LaTeX RGB color.
94 94 if len(color) == 7:
95 95 try:
96 96 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
97 97 textwrap.wrap(color[1:], 2)]))
98 except ValueError:
99 raise ValueError('Invalid color specification {}.'.format(color))
98 except ValueError as e:
99 raise ValueError('Invalid color specification {}.'.format(color)) from e
100 100 else:
101 101 raise ValueError('Invalid color specification {}.'.format(color))
102 102 else:
103 103 raise ValueError('No such backend {0}'.format(backend))
104 104 bin_data = f(s, wrap, color, scale)
105 105 if encode and bin_data:
106 106 bin_data = encodebytes(bin_data)
107 107 return bin_data
108 108
109 109
110 110 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
111 111 try:
112 112 from matplotlib import mathtext
113 113 from pyparsing import ParseFatalException
114 114 except ImportError:
115 115 return None
116 116
117 117 # mpl mathtext doesn't support display math, force inline
118 118 s = s.replace('$$', '$')
119 119 if wrap:
120 120 s = u'${0}$'.format(s)
121 121
122 122 try:
123 123 mt = mathtext.MathTextParser('bitmap')
124 124 f = BytesIO()
125 125 dpi = 120*scale
126 126 mt.to_png(f, s, fontsize=12, dpi=dpi, color=color)
127 127 return f.getvalue()
128 128 except (ValueError, RuntimeError, ParseFatalException):
129 129 return None
130 130
131 131
132 132 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
133 133 try:
134 134 find_cmd('latex')
135 135 find_cmd('dvipng')
136 136 except FindCmdError:
137 137 return None
138 138 try:
139 139 workdir = tempfile.mkdtemp()
140 140 tmpfile = os.path.join(workdir, "tmp.tex")
141 141 dvifile = os.path.join(workdir, "tmp.dvi")
142 142 outfile = os.path.join(workdir, "tmp.png")
143 143
144 144 with open(tmpfile, "w", encoding='utf8') as f:
145 145 f.writelines(genelatex(s, wrap))
146 146
147 147 with open(os.devnull, 'wb') as devnull:
148 148 subprocess.check_call(
149 149 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
150 150 cwd=workdir, stdout=devnull, stderr=devnull)
151 151
152 152 resolution = round(150*scale)
153 153 subprocess.check_call(
154 154 ["dvipng", "-T", "tight", "-D", str(resolution), "-z", "9",
155 155 "-bg", "transparent", "-o", outfile, dvifile, "-fg", color],
156 156 cwd=workdir, stdout=devnull, stderr=devnull)
157 157
158 158 with open(outfile, "rb") as f:
159 159 return f.read()
160 160 except subprocess.CalledProcessError:
161 161 return None
162 162 finally:
163 163 shutil.rmtree(workdir)
164 164
165 165
166 166 def kpsewhich(filename):
167 167 """Invoke kpsewhich command with an argument `filename`."""
168 168 try:
169 169 find_cmd("kpsewhich")
170 170 proc = subprocess.Popen(
171 171 ["kpsewhich", filename],
172 172 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
173 173 (stdout, stderr) = proc.communicate()
174 174 return stdout.strip().decode('utf8', 'replace')
175 175 except FindCmdError:
176 176 pass
177 177
178 178
179 179 def genelatex(body, wrap):
180 180 """Generate LaTeX document for dvipng backend."""
181 181 lt = LaTeXTool.instance()
182 182 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
183 183 yield r'\documentclass{article}'
184 184 packages = lt.packages
185 185 if breqn:
186 186 packages = packages + ['breqn']
187 187 for pack in packages:
188 188 yield r'\usepackage{{{0}}}'.format(pack)
189 189 yield r'\pagestyle{empty}'
190 190 if lt.preamble:
191 191 yield lt.preamble
192 192 yield r'\begin{document}'
193 193 if breqn:
194 194 yield r'\begin{dmath*}'
195 195 yield body
196 196 yield r'\end{dmath*}'
197 197 elif wrap:
198 198 yield u'$${0}$$'.format(body)
199 199 else:
200 200 yield body
201 201 yield u'\\end{document}'
202 202
203 203
204 204 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
205 205
206 206 def latex_to_html(s, alt='image'):
207 207 """Render LaTeX to HTML with embedded PNG data using data URIs.
208 208
209 209 Parameters
210 210 ----------
211 211 s : str
212 212 The raw string containing valid inline LateX.
213 213 alt : str
214 214 The alt text to use for the HTML.
215 215 """
216 216 base64_data = latex_to_png(s, encode=True).decode('ascii')
217 217 if base64_data:
218 218 return _data_uri_template_png % (base64_data, alt)
219 219
220 220
@@ -1,119 +1,119 b''
1 1 """Find files and directories which IPython uses.
2 2 """
3 3 import os.path
4 4 import shutil
5 5 import tempfile
6 6 from warnings import warn
7 7
8 8 import IPython
9 9 from IPython.utils.importstring import import_item
10 10 from IPython.utils.path import (
11 11 get_home_dir, get_xdg_dir, get_xdg_cache_dir, compress_user, _writable_dir,
12 12 ensure_dir_exists, fs_encoding)
13 13 from IPython.utils import py3compat
14 14
15 15 def get_ipython_dir() -> str:
16 16 """Get the IPython directory for this platform and user.
17 17
18 18 This uses the logic in `get_home_dir` to find the home directory
19 19 and then adds .ipython to the end of the path.
20 20 """
21 21
22 22 env = os.environ
23 23 pjoin = os.path.join
24 24
25 25
26 26 ipdir_def = '.ipython'
27 27
28 28 home_dir = get_home_dir()
29 29 xdg_dir = get_xdg_dir()
30 30
31 31 if 'IPYTHON_DIR' in env:
32 32 warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. '
33 33 'Please use IPYTHONDIR instead.', DeprecationWarning)
34 34 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
35 35 if ipdir is None:
36 36 # not set explicitly, use ~/.ipython
37 37 ipdir = pjoin(home_dir, ipdir_def)
38 38 if xdg_dir:
39 39 # Several IPython versions (up to 1.x) defaulted to .config/ipython
40 40 # on Linux. We have decided to go back to using .ipython everywhere
41 41 xdg_ipdir = pjoin(xdg_dir, 'ipython')
42 42
43 43 if _writable_dir(xdg_ipdir):
44 44 cu = compress_user
45 45 if os.path.exists(ipdir):
46 46 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
47 47 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
48 48 elif os.path.islink(xdg_ipdir):
49 49 warn(('{0} is deprecated. Move link to {1} to '
50 50 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
51 51 else:
52 52 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
53 53 shutil.move(xdg_ipdir, ipdir)
54 54
55 55 ipdir = os.path.normpath(os.path.expanduser(ipdir))
56 56
57 57 if os.path.exists(ipdir) and not _writable_dir(ipdir):
58 58 # ipdir exists, but is not writable
59 59 warn("IPython dir '{0}' is not a writable location,"
60 60 " using a temp directory.".format(ipdir))
61 61 ipdir = tempfile.mkdtemp()
62 62 elif not os.path.exists(ipdir):
63 63 parent = os.path.dirname(ipdir)
64 64 if not _writable_dir(parent):
65 65 # ipdir does not exist and parent isn't writable
66 66 warn("IPython parent '{0}' is not a writable location,"
67 67 " using a temp directory.".format(parent))
68 68 ipdir = tempfile.mkdtemp()
69 69 assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
70 70 return ipdir
71 71
72 72
73 73 def get_ipython_cache_dir() -> str:
74 74 """Get the cache directory it is created if it does not exist."""
75 75 xdgdir = get_xdg_cache_dir()
76 76 if xdgdir is None:
77 77 return get_ipython_dir()
78 78 ipdir = os.path.join(xdgdir, "ipython")
79 79 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
80 80 ensure_dir_exists(ipdir)
81 81 elif not _writable_dir(xdgdir):
82 82 return get_ipython_dir()
83 83
84 84 return ipdir
85 85
86 86
87 87 def get_ipython_package_dir() -> str:
88 88 """Get the base directory where IPython itself is installed."""
89 89 ipdir = os.path.dirname(IPython.__file__)
90 90 assert isinstance(ipdir, str)
91 91 return ipdir
92 92
93 93
94 94 def get_ipython_module_path(module_str):
95 95 """Find the path to an IPython module in this version of IPython.
96 96
97 97 This will always find the version of the module that is in this importable
98 98 IPython package. This will always return the path to the ``.py``
99 99 version of the module.
100 100 """
101 101 if module_str == 'IPython':
102 102 return os.path.join(get_ipython_package_dir(), '__init__.py')
103 103 mod = import_item(module_str)
104 104 the_path = mod.__file__.replace('.pyc', '.py')
105 105 the_path = the_path.replace('.pyo', '.py')
106 106 return py3compat.cast_unicode(the_path, fs_encoding)
107 107
108 108 def locate_profile(profile='default'):
109 109 """Find the path to the folder associated with a given profile.
110 110
111 111 I.e. find $IPYTHONDIR/profile_whatever.
112 112 """
113 113 from IPython.core.profiledir import ProfileDir, ProfileDirError
114 114 try:
115 115 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
116 except ProfileDirError:
116 except ProfileDirError as e:
117 117 # IOError makes more sense when people are expecting a path
118 raise IOError("Couldn't find profile %r" % profile)
118 raise IOError("Couldn't find profile %r" % profile) from e
119 119 return pd.location
@@ -1,155 +1,155 b''
1 1 """
2 2 Handlers for IPythonDirective's @doctest pseudo-decorator.
3 3
4 4 The Sphinx extension that provides support for embedded IPython code provides
5 5 a pseudo-decorator @doctest, which treats the input/output block as a
6 6 doctest, raising a RuntimeError during doc generation if the actual output
7 7 (after running the input) does not match the expected output.
8 8
9 9 An example usage is:
10 10
11 11 .. code-block:: rst
12 12
13 13 .. ipython::
14 14
15 15 In [1]: x = 1
16 16
17 17 @doctest
18 18 In [2]: x + 2
19 19 Out[3]: 3
20 20
21 21 One can also provide arguments to the decorator. The first argument should be
22 22 the name of a custom handler. The specification of any other arguments is
23 23 determined by the handler. For example,
24 24
25 25 .. code-block:: rst
26 26
27 27 .. ipython::
28 28
29 29 @doctest float
30 30 In [154]: 0.1 + 0.2
31 31 Out[154]: 0.3
32 32
33 33 allows the actual output ``0.30000000000000004`` to match the expected output
34 34 due to a comparison with `np.allclose`.
35 35
36 36 This module contains handlers for the @doctest pseudo-decorator. Handlers
37 37 should have the following function signature::
38 38
39 39 handler(sphinx_shell, args, input_lines, found, submitted)
40 40
41 41 where `sphinx_shell` is the embedded Sphinx shell, `args` contains the list
42 42 of arguments that follow: '@doctest handler_name', `input_lines` contains
43 43 a list of the lines relevant to the current doctest, `found` is a string
44 44 containing the output from the IPython shell, and `submitted` is a string
45 45 containing the expected output from the IPython shell.
46 46
47 47 Handlers must be registered in the `doctests` dict at the end of this module.
48 48
49 49 """
50 50
51 51 def str_to_array(s):
52 52 """
53 53 Simplistic converter of strings from repr to float NumPy arrays.
54 54
55 55 If the repr representation has ellipsis in it, then this will fail.
56 56
57 57 Parameters
58 58 ----------
59 59 s : str
60 60 The repr version of a NumPy array.
61 61
62 62 Examples
63 63 --------
64 64 >>> s = "array([ 0.3, inf, nan])"
65 65 >>> a = str_to_array(s)
66 66
67 67 """
68 68 import numpy as np
69 69
70 70 # Need to make sure eval() knows about inf and nan.
71 71 # This also assumes default printoptions for NumPy.
72 72 from numpy import inf, nan
73 73
74 74 if s.startswith(u'array'):
75 75 # Remove array( and )
76 76 s = s[6:-1]
77 77
78 78 if s.startswith(u'['):
79 79 a = np.array(eval(s), dtype=float)
80 80 else:
81 81 # Assume its a regular float. Force 1D so we can index into it.
82 82 a = np.atleast_1d(float(s))
83 83 return a
84 84
85 85 def float_doctest(sphinx_shell, args, input_lines, found, submitted):
86 86 """
87 87 Doctest which allow the submitted output to vary slightly from the input.
88 88
89 89 Here is how it might appear in an rst file:
90 90
91 91 .. code-block:: rst
92 92
93 93 .. ipython::
94 94
95 95 @doctest float
96 96 In [1]: 0.1 + 0.2
97 97 Out[1]: 0.3
98 98
99 99 """
100 100 import numpy as np
101 101
102 102 if len(args) == 2:
103 103 rtol = 1e-05
104 104 atol = 1e-08
105 105 else:
106 106 # Both must be specified if any are specified.
107 107 try:
108 108 rtol = float(args[2])
109 109 atol = float(args[3])
110 except IndexError:
110 except IndexError as e:
111 111 e = ("Both `rtol` and `atol` must be specified "
112 112 "if either are specified: {0}".format(args))
113 raise IndexError(e)
113 raise IndexError(e) from e
114 114
115 115 try:
116 116 submitted = str_to_array(submitted)
117 117 found = str_to_array(found)
118 118 except:
119 119 # For example, if the array is huge and there are ellipsis in it.
120 120 error = True
121 121 else:
122 122 found_isnan = np.isnan(found)
123 123 submitted_isnan = np.isnan(submitted)
124 124 error = not np.allclose(found_isnan, submitted_isnan)
125 125 error |= not np.allclose(found[~found_isnan],
126 126 submitted[~submitted_isnan],
127 127 rtol=rtol, atol=atol)
128 128
129 129 TAB = ' ' * 4
130 130 directive = sphinx_shell.directive
131 131 if directive is None:
132 132 source = 'Unavailable'
133 133 content = 'Unavailable'
134 134 else:
135 135 source = directive.state.document.current_source
136 136 # Add tabs and make into a single string.
137 137 content = '\n'.join([TAB + line for line in directive.content])
138 138
139 139 if error:
140 140
141 141 e = ('doctest float comparison failure\n\n'
142 142 'Document source: {0}\n\n'
143 143 'Raw content: \n{1}\n\n'
144 144 'On input line(s):\n{TAB}{2}\n\n'
145 145 'we found output:\n{TAB}{3}\n\n'
146 146 'instead of the expected:\n{TAB}{4}\n\n')
147 147 e = e.format(source, content, '\n'.join(input_lines), repr(found),
148 148 repr(submitted), TAB=TAB)
149 149 raise RuntimeError(e)
150 150
151 151 # dict of allowable doctest handlers. The key represents the first argument
152 152 # that must be given to @doctest in order to activate the handler.
153 153 doctests = {
154 154 'float': float_doctest,
155 155 }
@@ -1,203 +1,203 b''
1 1 """Extra magics for terminal use."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6
7 7 from logging import error
8 8 import os
9 9 import sys
10 10
11 11 from IPython.core.error import TryNext, UsageError
12 12 from IPython.core.magic import Magics, magics_class, line_magic
13 13 from IPython.lib.clipboard import ClipboardEmpty
14 14 from IPython.utils.text import SList, strip_email_quotes
15 15 from IPython.utils import py3compat
16 16
17 17 def get_pasted_lines(sentinel, l_input=py3compat.input, quiet=False):
18 18 """ Yield pasted lines until the user enters the given sentinel value.
19 19 """
20 20 if not quiet:
21 21 print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \
22 22 % sentinel)
23 23 prompt = ":"
24 24 else:
25 25 prompt = ""
26 26 while True:
27 27 try:
28 28 l = l_input(prompt)
29 29 if l == sentinel:
30 30 return
31 31 else:
32 32 yield l
33 33 except EOFError:
34 34 print('<EOF>')
35 35 return
36 36
37 37
38 38 @magics_class
39 39 class TerminalMagics(Magics):
40 40 def __init__(self, shell):
41 41 super(TerminalMagics, self).__init__(shell)
42 42
43 43 def store_or_execute(self, block, name):
44 44 """ Execute a block, or store it in a variable, per the user's request.
45 45 """
46 46 if name:
47 47 # If storing it for further editing
48 48 self.shell.user_ns[name] = SList(block.splitlines())
49 49 print("Block assigned to '%s'" % name)
50 50 else:
51 51 b = self.preclean_input(block)
52 52 self.shell.user_ns['pasted_block'] = b
53 53 self.shell.using_paste_magics = True
54 54 try:
55 55 self.shell.run_cell(b)
56 56 finally:
57 57 self.shell.using_paste_magics = False
58 58
59 59 def preclean_input(self, block):
60 60 lines = block.splitlines()
61 61 while lines and not lines[0].strip():
62 62 lines = lines[1:]
63 63 return strip_email_quotes('\n'.join(lines))
64 64
65 65 def rerun_pasted(self, name='pasted_block'):
66 66 """ Rerun a previously pasted command.
67 67 """
68 68 b = self.shell.user_ns.get(name)
69 69
70 70 # Sanity checks
71 71 if b is None:
72 72 raise UsageError('No previous pasted block available')
73 73 if not isinstance(b, str):
74 74 raise UsageError(
75 75 "Variable 'pasted_block' is not a string, can't execute")
76 76
77 77 print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)))
78 78 self.shell.run_cell(b)
79 79
80 80 @line_magic
81 81 def autoindent(self, parameter_s = ''):
82 82 """Toggle autoindent on/off (deprecated)"""
83 83 self.shell.set_autoindent()
84 84 print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent])
85 85
86 86 @line_magic
87 87 def cpaste(self, parameter_s=''):
88 88 """Paste & execute a pre-formatted code block from clipboard.
89 89
90 90 You must terminate the block with '--' (two minus-signs) or Ctrl-D
91 91 alone on the line. You can also provide your own sentinel with '%paste
92 92 -s %%' ('%%' is the new sentinel for this operation).
93 93
94 94 The block is dedented prior to execution to enable execution of method
95 95 definitions. '>' and '+' characters at the beginning of a line are
96 96 ignored, to allow pasting directly from e-mails, diff files and
97 97 doctests (the '...' continuation prompt is also stripped). The
98 98 executed block is also assigned to variable named 'pasted_block' for
99 99 later editing with '%edit pasted_block'.
100 100
101 101 You can also pass a variable name as an argument, e.g. '%cpaste foo'.
102 102 This assigns the pasted block to variable 'foo' as string, without
103 103 dedenting or executing it (preceding >>> and + is still stripped)
104 104
105 105 '%cpaste -r' re-executes the block previously entered by cpaste.
106 106 '%cpaste -q' suppresses any additional output messages.
107 107
108 108 Do not be alarmed by garbled output on Windows (it's a readline bug).
109 109 Just press enter and type -- (and press enter again) and the block
110 110 will be what was just pasted.
111 111
112 112 IPython statements (magics, shell escapes) are not supported (yet).
113 113
114 114 See also
115 115 --------
116 116 paste: automatically pull code from clipboard.
117 117
118 118 Examples
119 119 --------
120 120 ::
121 121
122 122 In [8]: %cpaste
123 123 Pasting code; enter '--' alone on the line to stop.
124 124 :>>> a = ["world!", "Hello"]
125 125 :>>> print " ".join(sorted(a))
126 126 :--
127 127 Hello world!
128 128 """
129 129 opts, name = self.parse_options(parameter_s, 'rqs:', mode='string')
130 130 if 'r' in opts:
131 131 self.rerun_pasted()
132 132 return
133 133
134 134 quiet = ('q' in opts)
135 135
136 136 sentinel = opts.get('s', u'--')
137 137 block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet))
138 138 self.store_or_execute(block, name)
139 139
140 140 @line_magic
141 141 def paste(self, parameter_s=''):
142 142 """Paste & execute a pre-formatted code block from clipboard.
143 143
144 144 The text is pulled directly from the clipboard without user
145 145 intervention and printed back on the screen before execution (unless
146 146 the -q flag is given to force quiet mode).
147 147
148 148 The block is dedented prior to execution to enable execution of method
149 149 definitions. '>' and '+' characters at the beginning of a line are
150 150 ignored, to allow pasting directly from e-mails, diff files and
151 151 doctests (the '...' continuation prompt is also stripped). The
152 152 executed block is also assigned to variable named 'pasted_block' for
153 153 later editing with '%edit pasted_block'.
154 154
155 155 You can also pass a variable name as an argument, e.g. '%paste foo'.
156 156 This assigns the pasted block to variable 'foo' as string, without
157 157 executing it (preceding >>> and + is still stripped).
158 158
159 159 Options:
160 160
161 161 -r: re-executes the block previously entered by cpaste.
162 162
163 163 -q: quiet mode: do not echo the pasted text back to the terminal.
164 164
165 165 IPython statements (magics, shell escapes) are not supported (yet).
166 166
167 167 See also
168 168 --------
169 169 cpaste: manually paste code into terminal until you mark its end.
170 170 """
171 171 opts, name = self.parse_options(parameter_s, 'rq', mode='string')
172 172 if 'r' in opts:
173 173 self.rerun_pasted()
174 174 return
175 175 try:
176 176 block = self.shell.hooks.clipboard_get()
177 177 except TryNext as clipboard_exc:
178 178 message = getattr(clipboard_exc, 'args')
179 179 if message:
180 180 error(message[0])
181 181 else:
182 182 error('Could not get text from the clipboard.')
183 183 return
184 except ClipboardEmpty:
185 raise UsageError("The clipboard appears to be empty")
184 except ClipboardEmpty as e:
185 raise UsageError("The clipboard appears to be empty") from e
186 186
187 187 # By default, echo back to terminal unless quiet mode is requested
188 188 if 'q' not in opts:
189 189 write = self.shell.write
190 190 write(self.shell.pycolorize(block))
191 191 if not block.endswith('\n'):
192 192 write('\n')
193 193 write("## -- End pasted text --\n")
194 194
195 195 self.store_or_execute(block, name)
196 196
197 197 # Class-level: add a '%cls' magic only on Windows
198 198 if sys.platform == 'win32':
199 199 @line_magic
200 200 def cls(self, s):
201 201 """Clear screen.
202 202 """
203 203 os.system("cls")
@@ -1,140 +1,140 b''
1 1 """GLUT Input hook for interactive use with prompt_toolkit
2 2 """
3 3
4 4
5 5 # GLUT is quite an old library and it is difficult to ensure proper
6 6 # integration within IPython since original GLUT does not allow to handle
7 7 # events one by one. Instead, it requires for the mainloop to be entered
8 8 # and never returned (there is not even a function to exit he
9 9 # mainloop). Fortunately, there are alternatives such as freeglut
10 10 # (available for linux and windows) and the OSX implementation gives
11 11 # access to a glutCheckLoop() function that blocks itself until a new
12 12 # event is received. This means we have to setup the idle callback to
13 13 # ensure we got at least one event that will unblock the function.
14 14 #
15 15 # Furthermore, it is not possible to install these handlers without a window
16 16 # being first created. We choose to make this window invisible. This means that
17 17 # display mode options are set at this level and user won't be able to change
18 18 # them later without modifying the code. This should probably be made available
19 19 # via IPython options system.
20 20
21 21 import sys
22 22 import time
23 23 import signal
24 24 import OpenGL.GLUT as glut
25 25 import OpenGL.platform as platform
26 26 from timeit import default_timer as clock
27 27
28 28 # Frame per second : 60
29 29 # Should probably be an IPython option
30 30 glut_fps = 60
31 31
32 32 # Display mode : double buffeed + rgba + depth
33 33 # Should probably be an IPython option
34 34 glut_display_mode = (glut.GLUT_DOUBLE |
35 35 glut.GLUT_RGBA |
36 36 glut.GLUT_DEPTH)
37 37
38 38 glutMainLoopEvent = None
39 39 if sys.platform == 'darwin':
40 40 try:
41 41 glutCheckLoop = platform.createBaseFunction(
42 42 'glutCheckLoop', dll=platform.GLUT, resultType=None,
43 43 argTypes=[],
44 44 doc='glutCheckLoop( ) -> None',
45 45 argNames=(),
46 46 )
47 except AttributeError:
47 except AttributeError as e:
48 48 raise RuntimeError(
49 49 '''Your glut implementation does not allow interactive sessions. '''
50 '''Consider installing freeglut.''')
50 '''Consider installing freeglut.''') from e
51 51 glutMainLoopEvent = glutCheckLoop
52 52 elif glut.HAVE_FREEGLUT:
53 53 glutMainLoopEvent = glut.glutMainLoopEvent
54 54 else:
55 55 raise RuntimeError(
56 56 '''Your glut implementation does not allow interactive sessions. '''
57 57 '''Consider installing freeglut.''')
58 58
59 59
60 60 def glut_display():
61 61 # Dummy display function
62 62 pass
63 63
64 64 def glut_idle():
65 65 # Dummy idle function
66 66 pass
67 67
68 68 def glut_close():
69 69 # Close function only hides the current window
70 70 glut.glutHideWindow()
71 71 glutMainLoopEvent()
72 72
73 73 def glut_int_handler(signum, frame):
74 74 # Catch sigint and print the defaultipyt message
75 75 signal.signal(signal.SIGINT, signal.default_int_handler)
76 76 print('\nKeyboardInterrupt')
77 77 # Need to reprint the prompt at this stage
78 78
79 79 # Initialisation code
80 80 glut.glutInit( sys.argv )
81 81 glut.glutInitDisplayMode( glut_display_mode )
82 82 # This is specific to freeglut
83 83 if bool(glut.glutSetOption):
84 84 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
85 85 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
86 86 glut.glutCreateWindow( b'ipython' )
87 87 glut.glutReshapeWindow( 1, 1 )
88 88 glut.glutHideWindow( )
89 89 glut.glutWMCloseFunc( glut_close )
90 90 glut.glutDisplayFunc( glut_display )
91 91 glut.glutIdleFunc( glut_idle )
92 92
93 93
94 94 def inputhook(context):
95 95 """Run the pyglet event loop by processing pending events only.
96 96
97 97 This keeps processing pending events until stdin is ready. After
98 98 processing all pending events, a call to time.sleep is inserted. This is
99 99 needed, otherwise, CPU usage is at 100%. This sleep time should be tuned
100 100 though for best performance.
101 101 """
102 102 # We need to protect against a user pressing Control-C when IPython is
103 103 # idle and this is running. We trap KeyboardInterrupt and pass.
104 104
105 105 signal.signal(signal.SIGINT, glut_int_handler)
106 106
107 107 try:
108 108 t = clock()
109 109
110 110 # Make sure the default window is set after a window has been closed
111 111 if glut.glutGetWindow() == 0:
112 112 glut.glutSetWindow( 1 )
113 113 glutMainLoopEvent()
114 114 return 0
115 115
116 116 while not context.input_is_ready():
117 117 glutMainLoopEvent()
118 118 # We need to sleep at this point to keep the idle CPU load
119 119 # low. However, if sleep to long, GUI response is poor. As
120 120 # a compromise, we watch how often GUI events are being processed
121 121 # and switch between a short and long sleep time. Here are some
122 122 # stats useful in helping to tune this.
123 123 # time CPU load
124 124 # 0.001 13%
125 125 # 0.005 3%
126 126 # 0.01 1.5%
127 127 # 0.05 0.5%
128 128 used_time = clock() - t
129 129 if used_time > 10.0:
130 130 # print 'Sleep for 1 s' # dbg
131 131 time.sleep(1.0)
132 132 elif used_time > 0.1:
133 133 # Few GUI events coming in, so we can sleep longer
134 134 # print 'Sleep for 0.05 s' # dbg
135 135 time.sleep(0.05)
136 136 else:
137 137 # Many GUI events coming in, so sleep only very little
138 138 time.sleep(0.001)
139 139 except KeyboardInterrupt:
140 140 pass
@@ -1,471 +1,471 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 import os
13 13 import re
14 14 import sys
15 15 import tempfile
16 16 import unittest
17 17
18 18 from contextlib import contextmanager
19 19 from io import StringIO
20 20 from subprocess import Popen, PIPE
21 21 from unittest.mock import patch
22 22
23 23 try:
24 24 # These tools are used by parts of the runtime, so we make the nose
25 25 # dependency optional at this point. Nose is a hard dependency to run the
26 26 # test suite, but NOT to use ipython itself.
27 27 import nose.tools as nt
28 28 has_nose = True
29 29 except ImportError:
30 30 has_nose = False
31 31
32 32 from traitlets.config.loader import Config
33 33 from IPython.utils.process import get_output_error_code
34 34 from IPython.utils.text import list_strings
35 35 from IPython.utils.io import temp_pyfile, Tee
36 36 from IPython.utils import py3compat
37 37
38 38 from . import decorators as dec
39 39 from . import skipdoctest
40 40
41 41
42 42 # The docstring for full_path doctests differently on win32 (different path
43 43 # separator) so just skip the doctest there. The example remains informative.
44 44 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
45 45
46 46 @doctest_deco
47 47 def full_path(startPath,files):
48 48 """Make full paths for all the listed files, based on startPath.
49 49
50 50 Only the base part of startPath is kept, since this routine is typically
51 51 used with a script's ``__file__`` variable as startPath. The base of startPath
52 52 is then prepended to all the listed files, forming the output list.
53 53
54 54 Parameters
55 55 ----------
56 56 startPath : string
57 57 Initial path to use as the base for the results. This path is split
58 58 using os.path.split() and only its first component is kept.
59 59
60 60 files : string or list
61 61 One or more files.
62 62
63 63 Examples
64 64 --------
65 65
66 66 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
67 67 ['/foo/a.txt', '/foo/b.txt']
68 68
69 69 >>> full_path('/foo',['a.txt','b.txt'])
70 70 ['/a.txt', '/b.txt']
71 71
72 72 If a single file is given, the output is still a list::
73 73
74 74 >>> full_path('/foo','a.txt')
75 75 ['/a.txt']
76 76 """
77 77
78 78 files = list_strings(files)
79 79 base = os.path.split(startPath)[0]
80 80 return [ os.path.join(base,f) for f in files ]
81 81
82 82
83 83 def parse_test_output(txt):
84 84 """Parse the output of a test run and return errors, failures.
85 85
86 86 Parameters
87 87 ----------
88 88 txt : str
89 89 Text output of a test run, assumed to contain a line of one of the
90 90 following forms::
91 91
92 92 'FAILED (errors=1)'
93 93 'FAILED (failures=1)'
94 94 'FAILED (errors=1, failures=1)'
95 95
96 96 Returns
97 97 -------
98 98 nerr, nfail
99 99 number of errors and failures.
100 100 """
101 101
102 102 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
103 103 if err_m:
104 104 nerr = int(err_m.group(1))
105 105 nfail = 0
106 106 return nerr, nfail
107 107
108 108 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
109 109 if fail_m:
110 110 nerr = 0
111 111 nfail = int(fail_m.group(1))
112 112 return nerr, nfail
113 113
114 114 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
115 115 re.MULTILINE)
116 116 if both_m:
117 117 nerr = int(both_m.group(1))
118 118 nfail = int(both_m.group(2))
119 119 return nerr, nfail
120 120
121 121 # If the input didn't match any of these forms, assume no error/failures
122 122 return 0, 0
123 123
124 124
125 125 # So nose doesn't think this is a test
126 126 parse_test_output.__test__ = False
127 127
128 128
129 129 def default_argv():
130 130 """Return a valid default argv for creating testing instances of ipython"""
131 131
132 132 return ['--quick', # so no config file is loaded
133 133 # Other defaults to minimize side effects on stdout
134 134 '--colors=NoColor', '--no-term-title','--no-banner',
135 135 '--autocall=0']
136 136
137 137
138 138 def default_config():
139 139 """Return a config object with good defaults for testing."""
140 140 config = Config()
141 141 config.TerminalInteractiveShell.colors = 'NoColor'
142 142 config.TerminalTerminalInteractiveShell.term_title = False,
143 143 config.TerminalInteractiveShell.autocall = 0
144 144 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
145 145 config.HistoryManager.hist_file = f.name
146 146 f.close()
147 147 config.HistoryManager.db_cache_size = 10000
148 148 return config
149 149
150 150
151 151 def get_ipython_cmd(as_string=False):
152 152 """
153 153 Return appropriate IPython command line name. By default, this will return
154 154 a list that can be used with subprocess.Popen, for example, but passing
155 155 `as_string=True` allows for returning the IPython command as a string.
156 156
157 157 Parameters
158 158 ----------
159 159 as_string: bool
160 160 Flag to allow to return the command as a string.
161 161 """
162 162 ipython_cmd = [sys.executable, "-m", "IPython"]
163 163
164 164 if as_string:
165 165 ipython_cmd = " ".join(ipython_cmd)
166 166
167 167 return ipython_cmd
168 168
169 169 def ipexec(fname, options=None, commands=()):
170 170 """Utility to call 'ipython filename'.
171 171
172 172 Starts IPython with a minimal and safe configuration to make startup as fast
173 173 as possible.
174 174
175 175 Note that this starts IPython in a subprocess!
176 176
177 177 Parameters
178 178 ----------
179 179 fname : str
180 180 Name of file to be executed (should have .py or .ipy extension).
181 181
182 182 options : optional, list
183 183 Extra command-line flags to be passed to IPython.
184 184
185 185 commands : optional, list
186 186 Commands to send in on stdin
187 187
188 188 Returns
189 189 -------
190 190 ``(stdout, stderr)`` of ipython subprocess.
191 191 """
192 192 if options is None: options = []
193 193
194 194 cmdargs = default_argv() + options
195 195
196 196 test_dir = os.path.dirname(__file__)
197 197
198 198 ipython_cmd = get_ipython_cmd()
199 199 # Absolute path for filename
200 200 full_fname = os.path.join(test_dir, fname)
201 201 full_cmd = ipython_cmd + cmdargs + [full_fname]
202 202 env = os.environ.copy()
203 203 # FIXME: ignore all warnings in ipexec while we have shims
204 204 # should we keep suppressing warnings here, even after removing shims?
205 205 env['PYTHONWARNINGS'] = 'ignore'
206 206 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
207 207 for k, v in env.items():
208 208 # Debug a bizarre failure we've seen on Windows:
209 209 # TypeError: environment can only contain strings
210 210 if not isinstance(v, str):
211 211 print(k, v)
212 212 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
213 213 out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
214 214 out, err = py3compat.decode(out), py3compat.decode(err)
215 215 # `import readline` causes 'ESC[?1034h' to be output sometimes,
216 216 # so strip that out before doing comparisons
217 217 if out:
218 218 out = re.sub(r'\x1b\[[^h]+h', '', out)
219 219 return out, err
220 220
221 221
222 222 def ipexec_validate(fname, expected_out, expected_err='',
223 223 options=None, commands=()):
224 224 """Utility to call 'ipython filename' and validate output/error.
225 225
226 226 This function raises an AssertionError if the validation fails.
227 227
228 228 Note that this starts IPython in a subprocess!
229 229
230 230 Parameters
231 231 ----------
232 232 fname : str
233 233 Name of the file to be executed (should have .py or .ipy extension).
234 234
235 235 expected_out : str
236 236 Expected stdout of the process.
237 237
238 238 expected_err : optional, str
239 239 Expected stderr of the process.
240 240
241 241 options : optional, list
242 242 Extra command-line flags to be passed to IPython.
243 243
244 244 Returns
245 245 -------
246 246 None
247 247 """
248 248
249 249 import nose.tools as nt
250 250
251 251 out, err = ipexec(fname, options, commands)
252 252 #print 'OUT', out # dbg
253 253 #print 'ERR', err # dbg
254 254 # If there are any errors, we must check those before stdout, as they may be
255 255 # more informative than simply having an empty stdout.
256 256 if err:
257 257 if expected_err:
258 258 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
259 259 else:
260 260 raise ValueError('Running file %r produced error: %r' %
261 261 (fname, err))
262 262 # If no errors or output on stderr was expected, match stdout
263 263 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
264 264
265 265
266 266 class TempFileMixin(unittest.TestCase):
267 267 """Utility class to create temporary Python/IPython files.
268 268
269 269 Meant as a mixin class for test cases."""
270 270
271 271 def mktmp(self, src, ext='.py'):
272 272 """Make a valid python temp file."""
273 273 fname = temp_pyfile(src, ext)
274 274 if not hasattr(self, 'tmps'):
275 275 self.tmps=[]
276 276 self.tmps.append(fname)
277 277 self.fname = fname
278 278
279 279 def tearDown(self):
280 280 # If the tmpfile wasn't made because of skipped tests, like in
281 281 # win32, there's nothing to cleanup.
282 282 if hasattr(self, 'tmps'):
283 283 for fname in self.tmps:
284 284 # If the tmpfile wasn't made because of skipped tests, like in
285 285 # win32, there's nothing to cleanup.
286 286 try:
287 287 os.unlink(fname)
288 288 except:
289 289 # On Windows, even though we close the file, we still can't
290 290 # delete it. I have no clue why
291 291 pass
292 292
293 293 def __enter__(self):
294 294 return self
295 295
296 296 def __exit__(self, exc_type, exc_value, traceback):
297 297 self.tearDown()
298 298
299 299
300 300 pair_fail_msg = ("Testing {0}\n\n"
301 301 "In:\n"
302 302 " {1!r}\n"
303 303 "Expected:\n"
304 304 " {2!r}\n"
305 305 "Got:\n"
306 306 " {3!r}\n")
307 307 def check_pairs(func, pairs):
308 308 """Utility function for the common case of checking a function with a
309 309 sequence of input/output pairs.
310 310
311 311 Parameters
312 312 ----------
313 313 func : callable
314 314 The function to be tested. Should accept a single argument.
315 315 pairs : iterable
316 316 A list of (input, expected_output) tuples.
317 317
318 318 Returns
319 319 -------
320 320 None. Raises an AssertionError if any output does not match the expected
321 321 value.
322 322 """
323 323 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
324 324 for inp, expected in pairs:
325 325 out = func(inp)
326 326 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
327 327
328 328
329 329 MyStringIO = StringIO
330 330
331 331 _re_type = type(re.compile(r''))
332 332
333 333 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
334 334 -------
335 335 {2!s}
336 336 -------
337 337 """
338 338
339 339 class AssertPrints(object):
340 340 """Context manager for testing that code prints certain text.
341 341
342 342 Examples
343 343 --------
344 344 >>> with AssertPrints("abc", suppress=False):
345 345 ... print("abcd")
346 346 ... print("def")
347 347 ...
348 348 abcd
349 349 def
350 350 """
351 351 def __init__(self, s, channel='stdout', suppress=True):
352 352 self.s = s
353 353 if isinstance(self.s, (str, _re_type)):
354 354 self.s = [self.s]
355 355 self.channel = channel
356 356 self.suppress = suppress
357 357
358 358 def __enter__(self):
359 359 self.orig_stream = getattr(sys, self.channel)
360 360 self.buffer = MyStringIO()
361 361 self.tee = Tee(self.buffer, channel=self.channel)
362 362 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
363 363
364 364 def __exit__(self, etype, value, traceback):
365 365 try:
366 366 if value is not None:
367 367 # If an error was raised, don't check anything else
368 368 return False
369 369 self.tee.flush()
370 370 setattr(sys, self.channel, self.orig_stream)
371 371 printed = self.buffer.getvalue()
372 372 for s in self.s:
373 373 if isinstance(s, _re_type):
374 374 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
375 375 else:
376 376 assert s in printed, notprinted_msg.format(s, self.channel, printed)
377 377 return False
378 378 finally:
379 379 self.tee.close()
380 380
381 381 printed_msg = """Found {0!r} in printed output (on {1}):
382 382 -------
383 383 {2!s}
384 384 -------
385 385 """
386 386
387 387 class AssertNotPrints(AssertPrints):
388 388 """Context manager for checking that certain output *isn't* produced.
389 389
390 390 Counterpart of AssertPrints"""
391 391 def __exit__(self, etype, value, traceback):
392 392 try:
393 393 if value is not None:
394 394 # If an error was raised, don't check anything else
395 395 self.tee.close()
396 396 return False
397 397 self.tee.flush()
398 398 setattr(sys, self.channel, self.orig_stream)
399 399 printed = self.buffer.getvalue()
400 400 for s in self.s:
401 401 if isinstance(s, _re_type):
402 402 assert not s.search(printed),printed_msg.format(
403 403 s.pattern, self.channel, printed)
404 404 else:
405 405 assert s not in printed, printed_msg.format(
406 406 s, self.channel, printed)
407 407 return False
408 408 finally:
409 409 self.tee.close()
410 410
411 411 @contextmanager
412 412 def mute_warn():
413 413 from IPython.utils import warn
414 414 save_warn = warn.warn
415 415 warn.warn = lambda *a, **kw: None
416 416 try:
417 417 yield
418 418 finally:
419 419 warn.warn = save_warn
420 420
421 421 @contextmanager
422 422 def make_tempfile(name):
423 423 """ Create an empty, named, temporary file for the duration of the context.
424 424 """
425 425 open(name, 'w').close()
426 426 try:
427 427 yield
428 428 finally:
429 429 os.unlink(name)
430 430
431 431 def fake_input(inputs):
432 432 """Temporarily replace the input() function to return the given values
433 433
434 434 Use as a context manager:
435 435
436 436 with fake_input(['result1', 'result2']):
437 437 ...
438 438
439 439 Values are returned in order. If input() is called again after the last value
440 440 was used, EOFError is raised.
441 441 """
442 442 it = iter(inputs)
443 443 def mock_input(prompt=''):
444 444 try:
445 445 return next(it)
446 except StopIteration:
447 raise EOFError('No more inputs given')
446 except StopIteration as e:
447 raise EOFError('No more inputs given') from e
448 448
449 449 return patch('builtins.input', mock_input)
450 450
451 451 def help_output_test(subcommand=''):
452 452 """test that `ipython [subcommand] -h` works"""
453 453 cmd = get_ipython_cmd() + [subcommand, '-h']
454 454 out, err, rc = get_output_error_code(cmd)
455 455 nt.assert_equal(rc, 0, err)
456 456 nt.assert_not_in("Traceback", err)
457 457 nt.assert_in("Options", out)
458 458 nt.assert_in("--help-all", out)
459 459 return out, err
460 460
461 461
462 462 def help_all_output_test(subcommand=''):
463 463 """test that `ipython [subcommand] --help-all` works"""
464 464 cmd = get_ipython_cmd() + [subcommand, '--help-all']
465 465 out, err, rc = get_output_error_code(cmd)
466 466 nt.assert_equal(rc, 0, err)
467 467 nt.assert_not_in("Traceback", err)
468 468 nt.assert_in("Options", out)
469 469 nt.assert_in("Class", out)
470 470 return out, err
471 471
@@ -1,205 +1,205 b''
1 1 """Windows-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 # stdlib
18 18 import os
19 19 import sys
20 20 import ctypes
21 21 import time
22 22
23 23 from ctypes import c_int, POINTER
24 24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 25 from subprocess import STDOUT, TimeoutExpired
26 26 from threading import Thread
27 27
28 28 # our own imports
29 29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 30 from . import py3compat
31 31 from .encoding import DEFAULT_ENCODING
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Function definitions
35 35 #-----------------------------------------------------------------------------
36 36
37 37 class AvoidUNCPath(object):
38 38 """A context manager to protect command execution from UNC paths.
39 39
40 40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 41 This context manager temporarily changes directory to the 'C:' drive on
42 42 entering, and restores the original working directory on exit.
43 43
44 44 The context manager returns the starting working directory *if* it made a
45 45 change and None otherwise, so that users can apply the necessary adjustment
46 46 to their system calls in the event of a change.
47 47
48 48 Examples
49 49 --------
50 50 ::
51 51 cmd = 'dir'
52 52 with AvoidUNCPath() as path:
53 53 if path is not None:
54 54 cmd = '"pushd %s &&"%s' % (path, cmd)
55 55 os.system(cmd)
56 56 """
57 57 def __enter__(self):
58 58 self.path = os.getcwd()
59 59 self.is_unc_path = self.path.startswith(r"\\")
60 60 if self.is_unc_path:
61 61 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 62 os.chdir("C:")
63 63 return self.path
64 64 else:
65 65 # We return None to signal that there was no change in the working
66 66 # directory
67 67 return None
68 68
69 69 def __exit__(self, exc_type, exc_value, traceback):
70 70 if self.is_unc_path:
71 71 os.chdir(self.path)
72 72
73 73
74 74 def _find_cmd(cmd):
75 75 """Find the full path to a .bat or .exe using the win32api module."""
76 76 try:
77 77 from win32api import SearchPath
78 except ImportError:
79 raise ImportError('you need to have pywin32 installed for this to work')
78 except ImportError as e:
79 raise ImportError('you need to have pywin32 installed for this to work') from e
80 80 else:
81 81 PATH = os.environ['PATH']
82 82 extensions = ['.exe', '.com', '.bat', '.py']
83 83 path = None
84 84 for ext in extensions:
85 85 try:
86 86 path = SearchPath(PATH, cmd, ext)[0]
87 87 except:
88 88 pass
89 89 if path is None:
90 90 raise OSError("command %r not found" % cmd)
91 91 else:
92 92 return path
93 93
94 94
95 95 def _system_body(p):
96 96 """Callback for _system."""
97 97 enc = DEFAULT_ENCODING
98 98
99 99 def stdout_read():
100 100 for line in read_no_interrupt(p.stdout).splitlines():
101 101 line = line.decode(enc, 'replace')
102 102 print(line, file=sys.stdout)
103 103
104 104 def stderr_read():
105 105 for line in read_no_interrupt(p.stderr).splitlines():
106 106 line = line.decode(enc, 'replace')
107 107 print(line, file=sys.stderr)
108 108
109 109 Thread(target=stdout_read).start()
110 110 Thread(target=stderr_read).start()
111 111
112 112 # Wait to finish for returncode. Unfortunately, Python has a bug where
113 113 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
114 114 # a loop instead of just doing `return p.wait()`.
115 115 while True:
116 116 result = p.poll()
117 117 if result is None:
118 118 time.sleep(0.01)
119 119 else:
120 120 return result
121 121
122 122
123 123 def system(cmd):
124 124 """Win32 version of os.system() that works with network shares.
125 125
126 126 Note that this implementation returns None, as meant for use in IPython.
127 127
128 128 Parameters
129 129 ----------
130 130 cmd : str or list
131 131 A command to be executed in the system shell.
132 132
133 133 Returns
134 134 -------
135 135 int : child process' exit code.
136 136 """
137 137 # The controller provides interactivity with both
138 138 # stdin and stdout
139 139 #import _process_win32_controller
140 140 #_process_win32_controller.system(cmd)
141 141
142 142 with AvoidUNCPath() as path:
143 143 if path is not None:
144 144 cmd = '"pushd %s &&"%s' % (path, cmd)
145 145 return process_handler(cmd, _system_body)
146 146
147 147 def getoutput(cmd):
148 148 """Return standard output of executing cmd in a shell.
149 149
150 150 Accepts the same arguments as os.system().
151 151
152 152 Parameters
153 153 ----------
154 154 cmd : str or list
155 155 A command to be executed in the system shell.
156 156
157 157 Returns
158 158 -------
159 159 stdout : str
160 160 """
161 161
162 162 with AvoidUNCPath() as path:
163 163 if path is not None:
164 164 cmd = '"pushd %s &&"%s' % (path, cmd)
165 165 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
166 166
167 167 if out is None:
168 168 out = b''
169 169 return py3compat.decode(out)
170 170
171 171 try:
172 172 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
173 173 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
174 174 CommandLineToArgvW.restype = POINTER(LPCWSTR)
175 175 LocalFree = ctypes.windll.kernel32.LocalFree
176 176 LocalFree.res_type = HLOCAL
177 177 LocalFree.arg_types = [HLOCAL]
178 178
179 179 def arg_split(commandline, posix=False, strict=True):
180 180 """Split a command line's arguments in a shell-like manner.
181 181
182 182 This is a special version for windows that use a ctypes call to CommandLineToArgvW
183 183 to do the argv splitting. The posix parameter is ignored.
184 184
185 185 If strict=False, process_common.arg_split(...strict=False) is used instead.
186 186 """
187 187 #CommandLineToArgvW returns path to executable if called with empty string.
188 188 if commandline.strip() == "":
189 189 return []
190 190 if not strict:
191 191 # not really a cl-arg, fallback on _process_common
192 192 return py_arg_split(commandline, posix=posix, strict=strict)
193 193 argvn = c_int()
194 194 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
195 195 result_array_type = LPCWSTR * argvn.value
196 196 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
197 197 retval = LocalFree(result_pointer)
198 198 return result
199 199 except AttributeError:
200 200 arg_split = py_arg_split
201 201
202 202 def check_pid(pid):
203 203 # OpenProcess returns 0 if no such process (of ours) exists
204 204 # positive int otherwise
205 205 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
@@ -1,187 +1,187 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for coloring text in ANSI terminals.
3 3 """
4 4
5 5 #*****************************************************************************
6 6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #*****************************************************************************
11 11
12 12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
13 13
14 14 import os
15 15
16 16 from IPython.utils.ipstruct import Struct
17 17
18 18 color_templates = (
19 19 # Dark colors
20 20 ("Black" , "0;30"),
21 21 ("Red" , "0;31"),
22 22 ("Green" , "0;32"),
23 23 ("Brown" , "0;33"),
24 24 ("Blue" , "0;34"),
25 25 ("Purple" , "0;35"),
26 26 ("Cyan" , "0;36"),
27 27 ("LightGray" , "0;37"),
28 28 # Light colors
29 29 ("DarkGray" , "1;30"),
30 30 ("LightRed" , "1;31"),
31 31 ("LightGreen" , "1;32"),
32 32 ("Yellow" , "1;33"),
33 33 ("LightBlue" , "1;34"),
34 34 ("LightPurple" , "1;35"),
35 35 ("LightCyan" , "1;36"),
36 36 ("White" , "1;37"),
37 37 # Blinking colors. Probably should not be used in anything serious.
38 38 ("BlinkBlack" , "5;30"),
39 39 ("BlinkRed" , "5;31"),
40 40 ("BlinkGreen" , "5;32"),
41 41 ("BlinkYellow" , "5;33"),
42 42 ("BlinkBlue" , "5;34"),
43 43 ("BlinkPurple" , "5;35"),
44 44 ("BlinkCyan" , "5;36"),
45 45 ("BlinkLightGray", "5;37"),
46 46 )
47 47
48 48 def make_color_table(in_class):
49 49 """Build a set of color attributes in a class.
50 50
51 51 Helper function for building the :class:`TermColors` and
52 52 :class`InputTermColors`.
53 53 """
54 54 for name,value in color_templates:
55 55 setattr(in_class,name,in_class._base % value)
56 56
57 57 class TermColors:
58 58 """Color escape sequences.
59 59
60 60 This class defines the escape sequences for all the standard (ANSI?)
61 61 colors in terminals. Also defines a NoColor escape which is just the null
62 62 string, suitable for defining 'dummy' color schemes in terminals which get
63 63 confused by color escapes.
64 64
65 65 This class should be used as a mixin for building color schemes."""
66 66
67 67 NoColor = '' # for color schemes in color-less terminals.
68 68 Normal = '\033[0m' # Reset normal coloring
69 69 _base = '\033[%sm' # Template for all other colors
70 70
71 71 # Build the actual color table as a set of class attributes:
72 72 make_color_table(TermColors)
73 73
74 74 class InputTermColors:
75 75 """Color escape sequences for input prompts.
76 76
77 77 This class is similar to TermColors, but the escapes are wrapped in \001
78 78 and \002 so that readline can properly know the length of each line and
79 79 can wrap lines accordingly. Use this class for any colored text which
80 80 needs to be used in input prompts, such as in calls to raw_input().
81 81
82 82 This class defines the escape sequences for all the standard (ANSI?)
83 83 colors in terminals. Also defines a NoColor escape which is just the null
84 84 string, suitable for defining 'dummy' color schemes in terminals which get
85 85 confused by color escapes.
86 86
87 87 This class should be used as a mixin for building color schemes."""
88 88
89 89 NoColor = '' # for color schemes in color-less terminals.
90 90
91 91 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
92 92 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
93 93 Normal = '\033[0m' # Reset normal coloring
94 94 _base = '\033[%sm' # Template for all other colors
95 95 else:
96 96 Normal = '\001\033[0m\002' # Reset normal coloring
97 97 _base = '\001\033[%sm\002' # Template for all other colors
98 98
99 99 # Build the actual color table as a set of class attributes:
100 100 make_color_table(InputTermColors)
101 101
102 102 class NoColors:
103 103 """This defines all the same names as the colour classes, but maps them to
104 104 empty strings, so it can easily be substituted to turn off colours."""
105 105 NoColor = ''
106 106 Normal = ''
107 107
108 108 for name, value in color_templates:
109 109 setattr(NoColors, name, '')
110 110
111 111 class ColorScheme:
112 112 """Generic color scheme class. Just a name and a Struct."""
113 113 def __init__(self,__scheme_name_,colordict=None,**colormap):
114 114 self.name = __scheme_name_
115 115 if colordict is None:
116 116 self.colors = Struct(**colormap)
117 117 else:
118 118 self.colors = Struct(colordict)
119 119
120 120 def copy(self,name=None):
121 121 """Return a full copy of the object, optionally renaming it."""
122 122 if name is None:
123 123 name = self.name
124 124 return ColorScheme(name, self.colors.dict())
125 125
126 126 class ColorSchemeTable(dict):
127 127 """General class to handle tables of color schemes.
128 128
129 129 It's basically a dict of color schemes with a couple of shorthand
130 130 attributes and some convenient methods.
131 131
132 132 active_scheme_name -> obvious
133 133 active_colors -> actual color table of the active scheme"""
134 134
135 135 def __init__(self, scheme_list=None, default_scheme=''):
136 136 """Create a table of color schemes.
137 137
138 138 The table can be created empty and manually filled or it can be
139 139 created with a list of valid color schemes AND the specification for
140 140 the default active scheme.
141 141 """
142 142
143 143 # create object attributes to be set later
144 144 self.active_scheme_name = ''
145 145 self.active_colors = None
146 146
147 147 if scheme_list:
148 148 if default_scheme == '':
149 149 raise ValueError('you must specify the default color scheme')
150 150 for scheme in scheme_list:
151 151 self.add_scheme(scheme)
152 152 self.set_active_scheme(default_scheme)
153 153
154 154 def copy(self):
155 155 """Return full copy of object"""
156 156 return ColorSchemeTable(self.values(),self.active_scheme_name)
157 157
158 158 def add_scheme(self,new_scheme):
159 159 """Add a new color scheme to the table."""
160 160 if not isinstance(new_scheme,ColorScheme):
161 161 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
162 162 self[new_scheme.name] = new_scheme
163 163
164 164 def set_active_scheme(self,scheme,case_sensitive=0):
165 165 """Set the currently active scheme.
166 166
167 167 Names are by default compared in a case-insensitive way, but this can
168 168 be changed by setting the parameter case_sensitive to true."""
169 169
170 170 scheme_names = list(self.keys())
171 171 if case_sensitive:
172 172 valid_schemes = scheme_names
173 173 scheme_test = scheme
174 174 else:
175 175 valid_schemes = [s.lower() for s in scheme_names]
176 176 scheme_test = scheme.lower()
177 177 try:
178 178 scheme_idx = valid_schemes.index(scheme_test)
179 except ValueError:
179 except ValueError as e:
180 180 raise ValueError('Unrecognized color scheme: ' + scheme + \
181 '\nValid schemes: '+str(scheme_names).replace("'', ",''))
181 '\nValid schemes: '+str(scheme_names).replace("'', ",'')) from e
182 182 else:
183 183 active = scheme_names[scheme_idx]
184 184 self.active_scheme_name = active
185 185 self.active_colors = self[active].colors
186 186 # Now allow using '' as an index for the current active scheme
187 187 self[''] = self[active]
@@ -1,39 +1,39 b''
1 1 # encoding: utf-8
2 2 """
3 3 A simple utility to import something by its string name.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9
10 10 def import_item(name):
11 11 """Import and return ``bar`` given the string ``foo.bar``.
12 12
13 13 Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
14 14 executing the code ``from foo import bar``.
15 15
16 16 Parameters
17 17 ----------
18 18 name : string
19 19 The fully qualified name of the module/package being imported.
20 20
21 21 Returns
22 22 -------
23 23 mod : module object
24 24 The module that was imported.
25 25 """
26 26
27 27 parts = name.rsplit('.', 1)
28 28 if len(parts) == 2:
29 29 # called with 'foo.bar....'
30 30 package, obj = parts
31 31 module = __import__(package, fromlist=[obj])
32 32 try:
33 33 pak = getattr(module, obj)
34 except AttributeError:
35 raise ImportError('No module named %s' % obj)
34 except AttributeError as e:
35 raise ImportError('No module named %s' % obj) from e
36 36 return pak
37 37 else:
38 38 # called with un-dotted string
39 39 return __import__(parts[0])
@@ -1,391 +1,391 b''
1 1 # encoding: utf-8
2 2 """A dict subclass that supports attribute style access.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez (original)
7 7 * Brian Granger (refactoring to a dict subclass)
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 __all__ = ['Struct']
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Code
25 25 #-----------------------------------------------------------------------------
26 26
27 27
28 28 class Struct(dict):
29 29 """A dict subclass with attribute style access.
30 30
31 31 This dict subclass has a a few extra features:
32 32
33 33 * Attribute style access.
34 34 * Protection of class members (like keys, items) when using attribute
35 35 style access.
36 36 * The ability to restrict assignment to only existing keys.
37 37 * Intelligent merging.
38 38 * Overloaded operators.
39 39 """
40 40 _allownew = True
41 41 def __init__(self, *args, **kw):
42 42 """Initialize with a dictionary, another Struct, or data.
43 43
44 44 Parameters
45 45 ----------
46 46 args : dict, Struct
47 47 Initialize with one dict or Struct
48 48 kw : dict
49 49 Initialize with key, value pairs.
50 50
51 51 Examples
52 52 --------
53 53
54 54 >>> s = Struct(a=10,b=30)
55 55 >>> s.a
56 56 10
57 57 >>> s.b
58 58 30
59 59 >>> s2 = Struct(s,c=30)
60 60 >>> sorted(s2.keys())
61 61 ['a', 'b', 'c']
62 62 """
63 63 object.__setattr__(self, '_allownew', True)
64 64 dict.__init__(self, *args, **kw)
65 65
66 66 def __setitem__(self, key, value):
67 67 """Set an item with check for allownew.
68 68
69 69 Examples
70 70 --------
71 71
72 72 >>> s = Struct()
73 73 >>> s['a'] = 10
74 74 >>> s.allow_new_attr(False)
75 75 >>> s['a'] = 10
76 76 >>> s['a']
77 77 10
78 78 >>> try:
79 79 ... s['b'] = 20
80 80 ... except KeyError:
81 81 ... print('this is not allowed')
82 82 ...
83 83 this is not allowed
84 84 """
85 85 if not self._allownew and key not in self:
86 86 raise KeyError(
87 87 "can't create new attribute %s when allow_new_attr(False)" % key)
88 88 dict.__setitem__(self, key, value)
89 89
90 90 def __setattr__(self, key, value):
91 91 """Set an attr with protection of class members.
92 92
93 93 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
94 94 :exc:`AttributeError`.
95 95
96 96 Examples
97 97 --------
98 98
99 99 >>> s = Struct()
100 100 >>> s.a = 10
101 101 >>> s.a
102 102 10
103 103 >>> try:
104 104 ... s.get = 10
105 105 ... except AttributeError:
106 106 ... print("you can't set a class member")
107 107 ...
108 108 you can't set a class member
109 109 """
110 110 # If key is an str it might be a class member or instance var
111 111 if isinstance(key, str):
112 112 # I can't simply call hasattr here because it calls getattr, which
113 113 # calls self.__getattr__, which returns True for keys in
114 114 # self._data. But I only want keys in the class and in
115 115 # self.__dict__
116 116 if key in self.__dict__ or hasattr(Struct, key):
117 117 raise AttributeError(
118 118 'attr %s is a protected member of class Struct.' % key
119 119 )
120 120 try:
121 121 self.__setitem__(key, value)
122 122 except KeyError as e:
123 raise AttributeError(e)
123 raise AttributeError(e) from e
124 124
125 125 def __getattr__(self, key):
126 126 """Get an attr by calling :meth:`dict.__getitem__`.
127 127
128 128 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
129 129 :exc:`AttributeError`.
130 130
131 131 Examples
132 132 --------
133 133
134 134 >>> s = Struct(a=10)
135 135 >>> s.a
136 136 10
137 137 >>> type(s.get)
138 138 <... 'builtin_function_or_method'>
139 139 >>> try:
140 140 ... s.b
141 141 ... except AttributeError:
142 142 ... print("I don't have that key")
143 143 ...
144 144 I don't have that key
145 145 """
146 146 try:
147 147 result = self[key]
148 except KeyError:
149 raise AttributeError(key)
148 except KeyError as e:
149 raise AttributeError(key) from e
150 150 else:
151 151 return result
152 152
153 153 def __iadd__(self, other):
154 154 """s += s2 is a shorthand for s.merge(s2).
155 155
156 156 Examples
157 157 --------
158 158
159 159 >>> s = Struct(a=10,b=30)
160 160 >>> s2 = Struct(a=20,c=40)
161 161 >>> s += s2
162 162 >>> sorted(s.keys())
163 163 ['a', 'b', 'c']
164 164 """
165 165 self.merge(other)
166 166 return self
167 167
168 168 def __add__(self,other):
169 169 """s + s2 -> New Struct made from s.merge(s2).
170 170
171 171 Examples
172 172 --------
173 173
174 174 >>> s1 = Struct(a=10,b=30)
175 175 >>> s2 = Struct(a=20,c=40)
176 176 >>> s = s1 + s2
177 177 >>> sorted(s.keys())
178 178 ['a', 'b', 'c']
179 179 """
180 180 sout = self.copy()
181 181 sout.merge(other)
182 182 return sout
183 183
184 184 def __sub__(self,other):
185 185 """s1 - s2 -> remove keys in s2 from s1.
186 186
187 187 Examples
188 188 --------
189 189
190 190 >>> s1 = Struct(a=10,b=30)
191 191 >>> s2 = Struct(a=40)
192 192 >>> s = s1 - s2
193 193 >>> s
194 194 {'b': 30}
195 195 """
196 196 sout = self.copy()
197 197 sout -= other
198 198 return sout
199 199
200 200 def __isub__(self,other):
201 201 """Inplace remove keys from self that are in other.
202 202
203 203 Examples
204 204 --------
205 205
206 206 >>> s1 = Struct(a=10,b=30)
207 207 >>> s2 = Struct(a=40)
208 208 >>> s1 -= s2
209 209 >>> s1
210 210 {'b': 30}
211 211 """
212 212 for k in other.keys():
213 213 if k in self:
214 214 del self[k]
215 215 return self
216 216
217 217 def __dict_invert(self, data):
218 218 """Helper function for merge.
219 219
220 220 Takes a dictionary whose values are lists and returns a dict with
221 221 the elements of each list as keys and the original keys as values.
222 222 """
223 223 outdict = {}
224 224 for k,lst in data.items():
225 225 if isinstance(lst, str):
226 226 lst = lst.split()
227 227 for entry in lst:
228 228 outdict[entry] = k
229 229 return outdict
230 230
231 231 def dict(self):
232 232 return self
233 233
234 234 def copy(self):
235 235 """Return a copy as a Struct.
236 236
237 237 Examples
238 238 --------
239 239
240 240 >>> s = Struct(a=10,b=30)
241 241 >>> s2 = s.copy()
242 242 >>> type(s2) is Struct
243 243 True
244 244 """
245 245 return Struct(dict.copy(self))
246 246
247 247 def hasattr(self, key):
248 248 """hasattr function available as a method.
249 249
250 250 Implemented like has_key.
251 251
252 252 Examples
253 253 --------
254 254
255 255 >>> s = Struct(a=10)
256 256 >>> s.hasattr('a')
257 257 True
258 258 >>> s.hasattr('b')
259 259 False
260 260 >>> s.hasattr('get')
261 261 False
262 262 """
263 263 return key in self
264 264
265 265 def allow_new_attr(self, allow = True):
266 266 """Set whether new attributes can be created in this Struct.
267 267
268 268 This can be used to catch typos by verifying that the attribute user
269 269 tries to change already exists in this Struct.
270 270 """
271 271 object.__setattr__(self, '_allownew', allow)
272 272
273 273 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
274 274 """Merge two Structs with customizable conflict resolution.
275 275
276 276 This is similar to :meth:`update`, but much more flexible. First, a
277 277 dict is made from data+key=value pairs. When merging this dict with
278 278 the Struct S, the optional dictionary 'conflict' is used to decide
279 279 what to do.
280 280
281 281 If conflict is not given, the default behavior is to preserve any keys
282 282 with their current value (the opposite of the :meth:`update` method's
283 283 behavior).
284 284
285 285 Parameters
286 286 ----------
287 287 __loc_data : dict, Struct
288 288 The data to merge into self
289 289 __conflict_solve : dict
290 290 The conflict policy dict. The keys are binary functions used to
291 291 resolve the conflict and the values are lists of strings naming
292 292 the keys the conflict resolution function applies to. Instead of
293 293 a list of strings a space separated string can be used, like
294 294 'a b c'.
295 295 kw : dict
296 296 Additional key, value pairs to merge in
297 297
298 298 Notes
299 299 -----
300 300
301 301 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
302 302 solve key conflicts. Here is an example::
303 303
304 304 __conflict_solve = dict(
305 305 func1=['a','b','c'],
306 306 func2=['d','e']
307 307 )
308 308
309 309 In this case, the function :func:`func1` will be used to resolve
310 310 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
311 311 keys 'd' and 'e'. This could also be written as::
312 312
313 313 __conflict_solve = dict(func1='a b c',func2='d e')
314 314
315 315 These functions will be called for each key they apply to with the
316 316 form::
317 317
318 318 func1(self['a'], other['a'])
319 319
320 320 The return value is used as the final merged value.
321 321
322 322 As a convenience, merge() provides five (the most commonly needed)
323 323 pre-defined policies: preserve, update, add, add_flip and add_s. The
324 324 easiest explanation is their implementation::
325 325
326 326 preserve = lambda old,new: old
327 327 update = lambda old,new: new
328 328 add = lambda old,new: old + new
329 329 add_flip = lambda old,new: new + old # note change of order!
330 330 add_s = lambda old,new: old + ' ' + new # only for str!
331 331
332 332 You can use those four words (as strings) as keys instead
333 333 of defining them as functions, and the merge method will substitute
334 334 the appropriate functions for you.
335 335
336 336 For more complicated conflict resolution policies, you still need to
337 337 construct your own functions.
338 338
339 339 Examples
340 340 --------
341 341
342 342 This show the default policy:
343 343
344 344 >>> s = Struct(a=10,b=30)
345 345 >>> s2 = Struct(a=20,c=40)
346 346 >>> s.merge(s2)
347 347 >>> sorted(s.items())
348 348 [('a', 10), ('b', 30), ('c', 40)]
349 349
350 350 Now, show how to specify a conflict dict:
351 351
352 352 >>> s = Struct(a=10,b=30)
353 353 >>> s2 = Struct(a=20,b=40)
354 354 >>> conflict = {'update':'a','add':'b'}
355 355 >>> s.merge(s2,conflict)
356 356 >>> sorted(s.items())
357 357 [('a', 20), ('b', 70)]
358 358 """
359 359
360 360 data_dict = dict(__loc_data__,**kw)
361 361
362 362 # policies for conflict resolution: two argument functions which return
363 363 # the value that will go in the new struct
364 364 preserve = lambda old,new: old
365 365 update = lambda old,new: new
366 366 add = lambda old,new: old + new
367 367 add_flip = lambda old,new: new + old # note change of order!
368 368 add_s = lambda old,new: old + ' ' + new
369 369
370 370 # default policy is to keep current keys when there's a conflict
371 371 conflict_solve = dict.fromkeys(self, preserve)
372 372
373 373 # the conflict_solve dictionary is given by the user 'inverted': we
374 374 # need a name-function mapping, it comes as a function -> names
375 375 # dict. Make a local copy (b/c we'll make changes), replace user
376 376 # strings for the three builtin policies and invert it.
377 377 if __conflict_solve:
378 378 inv_conflict_solve_user = __conflict_solve.copy()
379 379 for name, func in [('preserve',preserve), ('update',update),
380 380 ('add',add), ('add_flip',add_flip),
381 381 ('add_s',add_s)]:
382 382 if name in inv_conflict_solve_user.keys():
383 383 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
384 384 del inv_conflict_solve_user[name]
385 385 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
386 386 for key in data_dict:
387 387 if key not in self:
388 388 self[key] = data_dict[key]
389 389 else:
390 390 self[key] = conflict_solve[key](self[key],data_dict[key])
391 391
@@ -1,436 +1,436 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import os
10 10 import sys
11 11 import errno
12 12 import shutil
13 13 import random
14 14 import glob
15 15 from warnings import warn
16 16
17 17 from IPython.utils.process import system
18 18 from IPython.utils.decorators import undoc
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Code
22 22 #-----------------------------------------------------------------------------
23 23 fs_encoding = sys.getfilesystemencoding()
24 24
25 25 def _writable_dir(path):
26 26 """Whether `path` is a directory, to which the user has write access."""
27 27 return os.path.isdir(path) and os.access(path, os.W_OK)
28 28
29 29 if sys.platform == 'win32':
30 30 def _get_long_path_name(path):
31 31 """Get a long path name (expand ~) on Windows using ctypes.
32 32
33 33 Examples
34 34 --------
35 35
36 36 >>> get_long_path_name('c:\\docume~1')
37 37 'c:\\\\Documents and Settings'
38 38
39 39 """
40 40 try:
41 41 import ctypes
42 except ImportError:
43 raise ImportError('you need to have ctypes installed for this to work')
42 except ImportError as e:
43 raise ImportError('you need to have ctypes installed for this to work') from e
44 44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 46 ctypes.c_uint ]
47 47
48 48 buf = ctypes.create_unicode_buffer(260)
49 49 rv = _GetLongPathName(path, buf, 260)
50 50 if rv == 0 or rv > 260:
51 51 return path
52 52 else:
53 53 return buf.value
54 54 else:
55 55 def _get_long_path_name(path):
56 56 """Dummy no-op."""
57 57 return path
58 58
59 59
60 60
61 61 def get_long_path_name(path):
62 62 """Expand a path into its long form.
63 63
64 64 On Windows this expands any ~ in the paths. On other platforms, it is
65 65 a null operation.
66 66 """
67 67 return _get_long_path_name(path)
68 68
69 69
70 70 def unquote_filename(name, win32=(sys.platform=='win32')):
71 71 """ On Windows, remove leading and trailing quotes from filenames.
72 72
73 73 This function has been deprecated and should not be used any more:
74 74 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
75 75 """
76 76 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
77 77 "be used anymore", DeprecationWarning, stacklevel=2)
78 78 if win32:
79 79 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
80 80 name = name[1:-1]
81 81 return name
82 82
83 83
84 84 def compress_user(path):
85 85 """Reverse of :func:`os.path.expanduser`
86 86 """
87 87 home = os.path.expanduser('~')
88 88 if path.startswith(home):
89 89 path = "~" + path[len(home):]
90 90 return path
91 91
92 92 def get_py_filename(name, force_win32=None):
93 93 """Return a valid python filename in the current directory.
94 94
95 95 If the given name is not a file, it adds '.py' and searches again.
96 96 Raises IOError with an informative message if the file isn't found.
97 97 """
98 98
99 99 name = os.path.expanduser(name)
100 100 if force_win32 is not None:
101 101 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
102 102 "since IPython 5.0 and should not be used anymore",
103 103 DeprecationWarning, stacklevel=2)
104 104 if not os.path.isfile(name) and not name.endswith('.py'):
105 105 name += '.py'
106 106 if os.path.isfile(name):
107 107 return name
108 108 else:
109 109 raise IOError('File `%r` not found.' % name)
110 110
111 111
112 112 def filefind(filename, path_dirs=None):
113 113 """Find a file by looking through a sequence of paths.
114 114
115 115 This iterates through a sequence of paths looking for a file and returns
116 116 the full, absolute path of the first occurrence of the file. If no set of
117 117 path dirs is given, the filename is tested as is, after running through
118 118 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
119 119
120 120 filefind('myfile.txt')
121 121
122 122 will find the file in the current working dir, but::
123 123
124 124 filefind('~/myfile.txt')
125 125
126 126 Will find the file in the users home directory. This function does not
127 127 automatically try any paths, such as the cwd or the user's home directory.
128 128
129 129 Parameters
130 130 ----------
131 131 filename : str
132 132 The filename to look for.
133 133 path_dirs : str, None or sequence of str
134 134 The sequence of paths to look for the file in. If None, the filename
135 135 need to be absolute or be in the cwd. If a string, the string is
136 136 put into a sequence and the searched. If a sequence, walk through
137 137 each element and join with ``filename``, calling :func:`expandvars`
138 138 and :func:`expanduser` before testing for existence.
139 139
140 140 Returns
141 141 -------
142 142 Raises :exc:`IOError` or returns absolute path to file.
143 143 """
144 144
145 145 # If paths are quoted, abspath gets confused, strip them...
146 146 filename = filename.strip('"').strip("'")
147 147 # If the input is an absolute path, just check it exists
148 148 if os.path.isabs(filename) and os.path.isfile(filename):
149 149 return filename
150 150
151 151 if path_dirs is None:
152 152 path_dirs = ("",)
153 153 elif isinstance(path_dirs, str):
154 154 path_dirs = (path_dirs,)
155 155
156 156 for path in path_dirs:
157 157 if path == '.': path = os.getcwd()
158 158 testname = expand_path(os.path.join(path, filename))
159 159 if os.path.isfile(testname):
160 160 return os.path.abspath(testname)
161 161
162 162 raise IOError("File %r does not exist in any of the search paths: %r" %
163 163 (filename, path_dirs) )
164 164
165 165
166 166 class HomeDirError(Exception):
167 167 pass
168 168
169 169
170 170 def get_home_dir(require_writable=False) -> str:
171 171 """Return the 'home' directory, as a unicode string.
172 172
173 173 Uses os.path.expanduser('~'), and checks for writability.
174 174
175 175 See stdlib docs for how this is determined.
176 176 For Python <3.8, $HOME is first priority on *ALL* platforms.
177 177 For Python >=3.8 on Windows, %HOME% is no longer considered.
178 178
179 179 Parameters
180 180 ----------
181 181
182 182 require_writable : bool [default: False]
183 183 if True:
184 184 guarantees the return value is a writable directory, otherwise
185 185 raises HomeDirError
186 186 if False:
187 187 The path is resolved, but it is not guaranteed to exist or be writable.
188 188 """
189 189
190 190 homedir = os.path.expanduser('~')
191 191 # Next line will make things work even when /home/ is a symlink to
192 192 # /usr/home as it is on FreeBSD, for example
193 193 homedir = os.path.realpath(homedir)
194 194
195 195 if not _writable_dir(homedir) and os.name == 'nt':
196 196 # expanduser failed, use the registry to get the 'My Documents' folder.
197 197 try:
198 198 import winreg as wreg
199 199 with wreg.OpenKey(
200 200 wreg.HKEY_CURRENT_USER,
201 201 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
202 202 ) as key:
203 203 homedir = wreg.QueryValueEx(key,'Personal')[0]
204 204 except:
205 205 pass
206 206
207 207 if (not require_writable) or _writable_dir(homedir):
208 208 assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes"
209 209 return homedir
210 210 else:
211 211 raise HomeDirError('%s is not a writable dir, '
212 212 'set $HOME environment variable to override' % homedir)
213 213
214 214 def get_xdg_dir():
215 215 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
216 216
217 217 This is only for non-OS X posix (Linux,Unix,etc.) systems.
218 218 """
219 219
220 220 env = os.environ
221 221
222 222 if os.name == 'posix' and sys.platform != 'darwin':
223 223 # Linux, Unix, AIX, etc.
224 224 # use ~/.config if empty OR not set
225 225 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
226 226 if xdg and _writable_dir(xdg):
227 227 assert isinstance(xdg, str)
228 228 return xdg
229 229
230 230 return None
231 231
232 232
233 233 def get_xdg_cache_dir():
234 234 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
235 235
236 236 This is only for non-OS X posix (Linux,Unix,etc.) systems.
237 237 """
238 238
239 239 env = os.environ
240 240
241 241 if os.name == 'posix' and sys.platform != 'darwin':
242 242 # Linux, Unix, AIX, etc.
243 243 # use ~/.cache if empty OR not set
244 244 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
245 245 if xdg and _writable_dir(xdg):
246 246 assert isinstance(xdg, str)
247 247 return xdg
248 248
249 249 return None
250 250
251 251
252 252 @undoc
253 253 def get_ipython_dir():
254 254 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
255 255 from IPython.paths import get_ipython_dir
256 256 return get_ipython_dir()
257 257
258 258 @undoc
259 259 def get_ipython_cache_dir():
260 260 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
261 261 from IPython.paths import get_ipython_cache_dir
262 262 return get_ipython_cache_dir()
263 263
264 264 @undoc
265 265 def get_ipython_package_dir():
266 266 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
267 267 from IPython.paths import get_ipython_package_dir
268 268 return get_ipython_package_dir()
269 269
270 270 @undoc
271 271 def get_ipython_module_path(module_str):
272 272 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
273 273 from IPython.paths import get_ipython_module_path
274 274 return get_ipython_module_path(module_str)
275 275
276 276 @undoc
277 277 def locate_profile(profile='default'):
278 278 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
279 279 from IPython.paths import locate_profile
280 280 return locate_profile(profile=profile)
281 281
282 282 def expand_path(s):
283 283 """Expand $VARS and ~names in a string, like a shell
284 284
285 285 :Examples:
286 286
287 287 In [2]: os.environ['FOO']='test'
288 288
289 289 In [3]: expand_path('variable FOO is $FOO')
290 290 Out[3]: 'variable FOO is test'
291 291 """
292 292 # This is a pretty subtle hack. When expand user is given a UNC path
293 293 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
294 294 # the $ to get (\\server\share\%username%). I think it considered $
295 295 # alone an empty var. But, we need the $ to remains there (it indicates
296 296 # a hidden share).
297 297 if os.name=='nt':
298 298 s = s.replace('$\\', 'IPYTHON_TEMP')
299 299 s = os.path.expandvars(os.path.expanduser(s))
300 300 if os.name=='nt':
301 301 s = s.replace('IPYTHON_TEMP', '$\\')
302 302 return s
303 303
304 304
305 305 def unescape_glob(string):
306 306 """Unescape glob pattern in `string`."""
307 307 def unescape(s):
308 308 for pattern in '*[]!?':
309 309 s = s.replace(r'\{0}'.format(pattern), pattern)
310 310 return s
311 311 return '\\'.join(map(unescape, string.split('\\\\')))
312 312
313 313
314 314 def shellglob(args):
315 315 """
316 316 Do glob expansion for each element in `args` and return a flattened list.
317 317
318 318 Unmatched glob pattern will remain as-is in the returned list.
319 319
320 320 """
321 321 expanded = []
322 322 # Do not unescape backslash in Windows as it is interpreted as
323 323 # path separator:
324 324 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
325 325 for a in args:
326 326 expanded.extend(glob.glob(a) or [unescape(a)])
327 327 return expanded
328 328
329 329
330 330 def target_outdated(target,deps):
331 331 """Determine whether a target is out of date.
332 332
333 333 target_outdated(target,deps) -> 1/0
334 334
335 335 deps: list of filenames which MUST exist.
336 336 target: single filename which may or may not exist.
337 337
338 338 If target doesn't exist or is older than any file listed in deps, return
339 339 true, otherwise return false.
340 340 """
341 341 try:
342 342 target_time = os.path.getmtime(target)
343 343 except os.error:
344 344 return 1
345 345 for dep in deps:
346 346 dep_time = os.path.getmtime(dep)
347 347 if dep_time > target_time:
348 348 #print "For target",target,"Dep failed:",dep # dbg
349 349 #print "times (dep,tar):",dep_time,target_time # dbg
350 350 return 1
351 351 return 0
352 352
353 353
354 354 def target_update(target,deps,cmd):
355 355 """Update a target with a given command given a list of dependencies.
356 356
357 357 target_update(target,deps,cmd) -> runs cmd if target is outdated.
358 358
359 359 This is just a wrapper around target_outdated() which calls the given
360 360 command if target is outdated."""
361 361
362 362 if target_outdated(target,deps):
363 363 system(cmd)
364 364
365 365
366 366 ENOLINK = 1998
367 367
368 368 def link(src, dst):
369 369 """Hard links ``src`` to ``dst``, returning 0 or errno.
370 370
371 371 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
372 372 supported by the operating system.
373 373 """
374 374
375 375 if not hasattr(os, "link"):
376 376 return ENOLINK
377 377 link_errno = 0
378 378 try:
379 379 os.link(src, dst)
380 380 except OSError as e:
381 381 link_errno = e.errno
382 382 return link_errno
383 383
384 384
385 385 def link_or_copy(src, dst):
386 386 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
387 387
388 388 Attempts to maintain the semantics of ``shutil.copy``.
389 389
390 390 Because ``os.link`` does not overwrite files, a unique temporary file
391 391 will be used if the target already exists, then that file will be moved
392 392 into place.
393 393 """
394 394
395 395 if os.path.isdir(dst):
396 396 dst = os.path.join(dst, os.path.basename(src))
397 397
398 398 link_errno = link(src, dst)
399 399 if link_errno == errno.EEXIST:
400 400 if os.stat(src).st_ino == os.stat(dst).st_ino:
401 401 # dst is already a hard link to the correct file, so we don't need
402 402 # to do anything else. If we try to link and rename the file
403 403 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
404 404 return
405 405
406 406 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
407 407 try:
408 408 link_or_copy(src, new_dst)
409 409 except:
410 410 try:
411 411 os.remove(new_dst)
412 412 except OSError:
413 413 pass
414 414 raise
415 415 os.rename(new_dst, dst)
416 416 elif link_errno != 0:
417 417 # Either link isn't supported, or the filesystem doesn't support
418 418 # linking, or 'src' and 'dst' are on different filesystems.
419 419 shutil.copy(src, dst)
420 420
421 421 def ensure_dir_exists(path, mode=0o755):
422 422 """ensure that a directory exists
423 423
424 424 If it doesn't exist, try to create it and protect against a race condition
425 425 if another process is doing the same.
426 426
427 427 The default permissions are 755, which differ from os.makedirs default of 777.
428 428 """
429 429 if not os.path.exists(path):
430 430 try:
431 431 os.makedirs(path, mode=mode)
432 432 except OSError as e:
433 433 if e.errno != errno.EEXIST:
434 434 raise
435 435 elif not os.path.isdir(path):
436 436 raise IOError("%r exists but is not a directory" % path)
@@ -1,94 +1,94 b''
1 1 """A shim module for deprecated imports
2 2 """
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import sys
7 7 import types
8 8 from importlib import import_module
9 9
10 10 from .importstring import import_item
11 11
12 12
13 13 class ShimWarning(Warning):
14 14 """A warning to show when a module has moved, and a shim is in its place."""
15 15
16 16 class ShimImporter(object):
17 17 """Import hook for a shim.
18 18
19 19 This ensures that submodule imports return the real target module,
20 20 not a clone that will confuse `is` and `isinstance` checks.
21 21 """
22 22 def __init__(self, src, mirror):
23 23 self.src = src
24 24 self.mirror = mirror
25 25
26 26 def _mirror_name(self, fullname):
27 27 """get the name of the mirrored module"""
28 28
29 29 return self.mirror + fullname[len(self.src):]
30 30
31 31 def find_module(self, fullname, path=None):
32 32 """Return self if we should be used to import the module."""
33 33 if fullname.startswith(self.src + '.'):
34 34 mirror_name = self._mirror_name(fullname)
35 35 try:
36 36 mod = import_item(mirror_name)
37 37 except ImportError:
38 38 return
39 39 else:
40 40 if not isinstance(mod, types.ModuleType):
41 41 # not a module
42 42 return None
43 43 return self
44 44
45 45 def load_module(self, fullname):
46 46 """Import the mirrored module, and insert it into sys.modules"""
47 47 mirror_name = self._mirror_name(fullname)
48 48 mod = import_item(mirror_name)
49 49 sys.modules[fullname] = mod
50 50 return mod
51 51
52 52
53 53 class ShimModule(types.ModuleType):
54 54
55 55 def __init__(self, *args, **kwargs):
56 56 self._mirror = kwargs.pop("mirror")
57 57 src = kwargs.pop("src", None)
58 58 if src:
59 59 kwargs['name'] = src.rsplit('.', 1)[-1]
60 60 super(ShimModule, self).__init__(*args, **kwargs)
61 61 # add import hook for descendent modules
62 62 if src:
63 63 sys.meta_path.append(
64 64 ShimImporter(src=src, mirror=self._mirror)
65 65 )
66 66
67 67 @property
68 68 def __path__(self):
69 69 return []
70 70
71 71 @property
72 72 def __spec__(self):
73 73 """Don't produce __spec__ until requested"""
74 74 return import_module(self._mirror).__spec__
75 75
76 76 def __dir__(self):
77 77 return dir(import_module(self._mirror))
78 78
79 79 @property
80 80 def __all__(self):
81 81 """Ensure __all__ is always defined"""
82 82 mod = import_module(self._mirror)
83 83 try:
84 84 return mod.__all__
85 85 except AttributeError:
86 86 return [name for name in dir(mod) if not name.startswith('_')]
87 87
88 88 def __getattr__(self, key):
89 89 # Use the equivalent of import_item(name), see below
90 90 name = "%s.%s" % (self._mirror, key)
91 91 try:
92 92 return import_item(name)
93 except ImportError:
94 raise AttributeError(key)
93 except ImportError as e:
94 raise AttributeError(key) from e
@@ -1,160 +1,160 b''
1 1 """Define text roles for GitHub
2 2
3 3 * ghissue - Issue
4 4 * ghpull - Pull Request
5 5 * ghuser - User
6 6
7 7 Adapted from bitbucket example here:
8 8 https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py
9 9
10 10 Authors
11 11 -------
12 12
13 13 * Doug Hellmann
14 14 * Min RK
15 15 """
16 16 #
17 17 # Original Copyright (c) 2010 Doug Hellmann. All rights reserved.
18 18 #
19 19
20 20 from docutils import nodes, utils
21 21 from docutils.parsers.rst.roles import set_classes
22 22 from sphinx.util.logging import getLogger
23 23
24 24 info = getLogger(__name__).info
25 25
26 26 def make_link_node(rawtext, app, type, slug, options):
27 27 """Create a link to a github resource.
28 28
29 29 :param rawtext: Text being replaced with link node.
30 30 :param app: Sphinx application context
31 31 :param type: Link type (issues, changeset, etc.)
32 32 :param slug: ID of the thing to link to
33 33 :param options: Options dictionary passed to role func.
34 34 """
35 35
36 36 try:
37 37 base = app.config.github_project_url
38 38 if not base:
39 39 raise AttributeError
40 40 if not base.endswith('/'):
41 41 base += '/'
42 42 except AttributeError as err:
43 raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
43 raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) from err
44 44
45 45 ref = base + type + '/' + slug + '/'
46 46 set_classes(options)
47 47 prefix = "#"
48 48 if type == 'pull':
49 49 prefix = "PR " + prefix
50 50 node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
51 51 **options)
52 52 return node
53 53
54 54 def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
55 55 """Link to a GitHub issue.
56 56
57 57 Returns 2 part tuple containing list of nodes to insert into the
58 58 document and a list of system messages. Both are allowed to be
59 59 empty.
60 60
61 61 :param name: The role name used in the document.
62 62 :param rawtext: The entire markup snippet, with role.
63 63 :param text: The text marked with the role.
64 64 :param lineno: The line number where rawtext appears in the input.
65 65 :param inliner: The inliner instance that called us.
66 66 :param options: Directive options for customization.
67 67 :param content: The directive content for customization.
68 68 """
69 69
70 70 try:
71 71 issue_num = int(text)
72 72 if issue_num <= 0:
73 73 raise ValueError
74 74 except ValueError:
75 75 msg = inliner.reporter.error(
76 76 'GitHub issue number must be a number greater than or equal to 1; '
77 77 '"%s" is invalid.' % text, line=lineno)
78 78 prb = inliner.problematic(rawtext, rawtext, msg)
79 79 return [prb], [msg]
80 80 app = inliner.document.settings.env.app
81 81 #info('issue %r' % text)
82 82 if 'pull' in name.lower():
83 83 category = 'pull'
84 84 elif 'issue' in name.lower():
85 85 category = 'issues'
86 86 else:
87 87 msg = inliner.reporter.error(
88 88 'GitHub roles include "ghpull" and "ghissue", '
89 89 '"%s" is invalid.' % name, line=lineno)
90 90 prb = inliner.problematic(rawtext, rawtext, msg)
91 91 return [prb], [msg]
92 92 node = make_link_node(rawtext, app, category, str(issue_num), options)
93 93 return [node], []
94 94
95 95 def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
96 96 """Link to a GitHub user.
97 97
98 98 Returns 2 part tuple containing list of nodes to insert into the
99 99 document and a list of system messages. Both are allowed to be
100 100 empty.
101 101
102 102 :param name: The role name used in the document.
103 103 :param rawtext: The entire markup snippet, with role.
104 104 :param text: The text marked with the role.
105 105 :param lineno: The line number where rawtext appears in the input.
106 106 :param inliner: The inliner instance that called us.
107 107 :param options: Directive options for customization.
108 108 :param content: The directive content for customization.
109 109 """
110 110 app = inliner.document.settings.env.app
111 111 #info('user link %r' % text)
112 112 ref = 'https://www.github.com/' + text
113 113 node = nodes.reference(rawtext, text, refuri=ref, **options)
114 114 return [node], []
115 115
116 116 def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
117 117 """Link to a GitHub commit.
118 118
119 119 Returns 2 part tuple containing list of nodes to insert into the
120 120 document and a list of system messages. Both are allowed to be
121 121 empty.
122 122
123 123 :param name: The role name used in the document.
124 124 :param rawtext: The entire markup snippet, with role.
125 125 :param text: The text marked with the role.
126 126 :param lineno: The line number where rawtext appears in the input.
127 127 :param inliner: The inliner instance that called us.
128 128 :param options: Directive options for customization.
129 129 :param content: The directive content for customization.
130 130 """
131 131 app = inliner.document.settings.env.app
132 132 #info('user link %r' % text)
133 133 try:
134 134 base = app.config.github_project_url
135 135 if not base:
136 136 raise AttributeError
137 137 if not base.endswith('/'):
138 138 base += '/'
139 139 except AttributeError as err:
140 raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
140 raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) from err
141 141
142 142 ref = base + text
143 143 node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
144 144 return [node], []
145 145
146 146
147 147 def setup(app):
148 148 """Install the plugin.
149 149
150 150 :param app: Sphinx application context.
151 151 """
152 152 info('Initializing GitHub plugin')
153 153 app.add_role('ghissue', ghissue_role)
154 154 app.add_role('ghpull', ghissue_role)
155 155 app.add_role('ghuser', ghuser_role)
156 156 app.add_role('ghcommit', ghcommit_role)
157 157 app.add_config_value('github_project_url', None, 'env')
158 158
159 159 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
160 160 return metadata
@@ -1,303 +1,303 b''
1 1 """Functions for Github API requests."""
2 2
3 3 try:
4 4 input = raw_input
5 5 except NameError:
6 6 pass
7 7
8 8 import os
9 9 import re
10 10 import sys
11 11
12 12 import requests
13 13 import getpass
14 14 import json
15 15
16 16 try:
17 17 import requests_cache
18 18 except ImportError:
19 19 print("cache not available, install `requests_cache` for caching.", file=sys.stderr)
20 20 else:
21 21 requests_cache.install_cache("gh_api", expire_after=3600)
22 22
23 23 # Keyring stores passwords by a 'username', but we're not storing a username and
24 24 # password
25 25 import socket
26 26 fake_username = 'ipython_tools_%s' % socket.gethostname().replace('.','_').replace('-','_')
27 27
28 28 class Obj(dict):
29 29 """Dictionary with attribute access to names."""
30 30 def __getattr__(self, name):
31 31 try:
32 32 return self[name]
33 except KeyError:
34 raise AttributeError(name)
33 except KeyError as e:
34 raise AttributeError(name) from e
35 35
36 36 def __setattr__(self, name, val):
37 37 self[name] = val
38 38
39 39 token = None
40 40 def get_auth_token():
41 41 global token
42 42
43 43 if token is not None:
44 44 return token
45 45
46 46 import keyring
47 47 token = keyring.get_password('github', fake_username)
48 48 if token is not None:
49 49 return token
50 50
51 51 print("Please enter your github username and password. These are not "
52 52 "stored, only used to get an oAuth token. You can revoke this at "
53 53 "any time on Github.\n"
54 54 "Username: ", file=sys.stderr, end='')
55 55 user = input('')
56 56 pw = getpass.getpass("Password: ", stream=sys.stderr)
57 57
58 58 auth_request = {
59 59 "scopes": [
60 60 "public_repo",
61 61 "gist"
62 62 ],
63 63 "note": "IPython tools %s" % socket.gethostname(),
64 64 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
65 65 }
66 66 response = requests.post('https://api.github.com/authorizations',
67 67 auth=(user, pw), data=json.dumps(auth_request))
68 68 if response.status_code == 401 and \
69 69 'required;' in response.headers.get('X-GitHub-OTP', ''):
70 70 print("Your login API requested a one time password", file=sys.stderr)
71 71 otp = getpass.getpass("One Time Password: ", stream=sys.stderr)
72 72 response = requests.post('https://api.github.com/authorizations',
73 73 auth=(user, pw),
74 74 data=json.dumps(auth_request),
75 75 headers={'X-GitHub-OTP':otp})
76 76 response.raise_for_status()
77 77 token = json.loads(response.text)['token']
78 78 keyring.set_password('github', fake_username, token)
79 79 return token
80 80
81 81 def make_auth_header():
82 82 return {'Authorization': 'token ' + get_auth_token()}
83 83
84 84 def post_issue_comment(project, num, body):
85 85 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
86 86 payload = json.dumps({'body': body})
87 87 requests.post(url, data=payload, headers=make_auth_header())
88 88
89 89 def post_gist(content, description='', filename='file', auth=False):
90 90 """Post some text to a Gist, and return the URL."""
91 91 post_data = json.dumps({
92 92 "description": description,
93 93 "public": True,
94 94 "files": {
95 95 filename: {
96 96 "content": content
97 97 }
98 98 }
99 99 }).encode('utf-8')
100 100
101 101 headers = make_auth_header() if auth else {}
102 102 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
103 103 response.raise_for_status()
104 104 response_data = json.loads(response.text)
105 105 return response_data['html_url']
106 106
107 107 def get_pull_request(project, num, auth=False):
108 108 """get pull request info by number
109 109 """
110 110 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
111 111 if auth:
112 112 header = make_auth_header()
113 113 else:
114 114 header = None
115 115 print("fetching %s" % url, file=sys.stderr)
116 116 response = requests.get(url, headers=header)
117 117 response.raise_for_status()
118 118 return json.loads(response.text, object_hook=Obj)
119 119
120 120 def get_pull_request_files(project, num, auth=False):
121 121 """get list of files in a pull request"""
122 122 url = "https://api.github.com/repos/{project}/pulls/{num}/files".format(project=project, num=num)
123 123 if auth:
124 124 header = make_auth_header()
125 125 else:
126 126 header = None
127 127 return get_paged_request(url, headers=header)
128 128
129 129 element_pat = re.compile(r'<(.+?)>')
130 130 rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')
131 131
132 132 def get_paged_request(url, headers=None, **params):
133 133 """get a full list, handling APIv3's paging"""
134 134 results = []
135 135 params.setdefault("per_page", 100)
136 136 while True:
137 137 if '?' in url:
138 138 params = None
139 139 print("fetching %s" % url, file=sys.stderr)
140 140 else:
141 141 print("fetching %s with %s" % (url, params), file=sys.stderr)
142 142 response = requests.get(url, headers=headers, params=params)
143 143 response.raise_for_status()
144 144 results.extend(response.json())
145 145 if 'next' in response.links:
146 146 url = response.links['next']['url']
147 147 else:
148 148 break
149 149 return results
150 150
151 151 def get_pulls_list(project, auth=False, **params):
152 152 """get pull request list"""
153 153 params.setdefault("state", "closed")
154 154 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
155 155 if auth:
156 156 headers = make_auth_header()
157 157 else:
158 158 headers = None
159 159 pages = get_paged_request(url, headers=headers, **params)
160 160 return pages
161 161
162 162 def get_issues_list(project, auth=False, **params):
163 163 """get issues list"""
164 164 params.setdefault("state", "closed")
165 165 url = "https://api.github.com/repos/{project}/issues".format(project=project)
166 166 if auth:
167 167 headers = make_auth_header()
168 168 else:
169 169 headers = None
170 170 pages = get_paged_request(url, headers=headers, **params)
171 171 return pages
172 172
173 173 def get_milestones(project, auth=False, **params):
174 174 params.setdefault('state', 'all')
175 175 url = "https://api.github.com/repos/{project}/milestones".format(project=project)
176 176 if auth:
177 177 headers = make_auth_header()
178 178 else:
179 179 headers = None
180 180 milestones = get_paged_request(url, headers=headers, **params)
181 181 return milestones
182 182
183 183 def get_milestone_id(project, milestone, auth=False, **params):
184 184 milestones = get_milestones(project, auth=auth, **params)
185 185 for mstone in milestones:
186 186 if mstone['title'] == milestone:
187 187 return mstone['number']
188 188 else:
189 189 raise ValueError("milestone %s not found" % milestone)
190 190
191 191 def is_pull_request(issue):
192 192 """Return True if the given issue is a pull request."""
193 193 return bool(issue.get('pull_request', {}).get('html_url', None))
194 194
195 195 def get_authors(pr):
196 196 print("getting authors for #%i" % pr['number'], file=sys.stderr)
197 197 h = make_auth_header()
198 198 r = requests.get(pr['commits_url'], headers=h)
199 199 r.raise_for_status()
200 200 commits = r.json()
201 201 authors = []
202 202 for commit in commits:
203 203 author = commit['commit']['author']
204 204 authors.append("%s <%s>" % (author['name'], author['email']))
205 205 return authors
206 206
207 207 # encode_multipart_formdata is from urllib3.filepost
208 208 # The only change is to iter_fields, to enforce S3's required key ordering
209 209
210 210 def iter_fields(fields):
211 211 fields = fields.copy()
212 212 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
213 213 'Policy', 'Signature', 'Content-Type', 'file'):
214 214 yield (key, fields.pop(key))
215 215 for (k,v) in fields.items():
216 216 yield k,v
217 217
218 218 def encode_multipart_formdata(fields, boundary=None):
219 219 """
220 220 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
221 221
222 222 :param fields:
223 223 Dictionary of fields or list of (key, value) field tuples. The key is
224 224 treated as the field name, and the value as the body of the form-data
225 225 bytes. If the value is a tuple of two elements, then the first element
226 226 is treated as the filename of the form-data section.
227 227
228 228 Field names and filenames must be unicode.
229 229
230 230 :param boundary:
231 231 If not specified, then a random boundary will be generated using
232 232 :func:`mimetools.choose_boundary`.
233 233 """
234 234 # copy requests imports in here:
235 235 from io import BytesIO
236 236 from requests.packages.urllib3.filepost import (
237 237 choose_boundary, six, writer, b, get_content_type
238 238 )
239 239 body = BytesIO()
240 240 if boundary is None:
241 241 boundary = choose_boundary()
242 242
243 243 for fieldname, value in iter_fields(fields):
244 244 body.write(b('--%s\r\n' % (boundary)))
245 245
246 246 if isinstance(value, tuple):
247 247 filename, data = value
248 248 writer(body).write('Content-Disposition: form-data; name="%s"; '
249 249 'filename="%s"\r\n' % (fieldname, filename))
250 250 body.write(b('Content-Type: %s\r\n\r\n' %
251 251 (get_content_type(filename))))
252 252 else:
253 253 data = value
254 254 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
255 255 % (fieldname))
256 256 body.write(b'Content-Type: text/plain\r\n\r\n')
257 257
258 258 if isinstance(data, int):
259 259 data = str(data) # Backwards compatibility
260 260 if isinstance(data, six.text_type):
261 261 writer(body).write(data)
262 262 else:
263 263 body.write(data)
264 264
265 265 body.write(b'\r\n')
266 266
267 267 body.write(b('--%s--\r\n' % (boundary)))
268 268
269 269 content_type = b('multipart/form-data; boundary=%s' % boundary)
270 270
271 271 return body.getvalue(), content_type
272 272
273 273
274 274 def post_download(project, filename, name=None, description=""):
275 275 """Upload a file to the GitHub downloads area"""
276 276 if name is None:
277 277 name = os.path.basename(filename)
278 278 with open(filename, 'rb') as f:
279 279 filedata = f.read()
280 280
281 281 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
282 282
283 283 payload = json.dumps(dict(name=name, size=len(filedata),
284 284 description=description))
285 285 response = requests.post(url, data=payload, headers=make_auth_header())
286 286 response.raise_for_status()
287 287 reply = json.loads(response.content)
288 288 s3_url = reply['s3_url']
289 289
290 290 fields = dict(
291 291 key=reply['path'],
292 292 acl=reply['acl'],
293 293 success_action_status=201,
294 294 Filename=reply['name'],
295 295 AWSAccessKeyId=reply['accesskeyid'],
296 296 Policy=reply['policy'],
297 297 Signature=reply['signature'],
298 298 file=(reply['name'], filedata),
299 299 )
300 300 fields['Content-Type'] = reply['mime_type']
301 301 data, content_type = encode_multipart_formdata(fields)
302 302 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
303 303 return s3r
General Comments 0
You need to be logged in to leave comments. Login now