Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/utils/openpy.py: 29%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

49 statements  

1""" 

2Tools to open .py files as Unicode, using the encoding specified within the file, 

3as per PEP 263. 

4 

5Much of the code is taken from the tokenize module in Python 3.2. 

6""" 

7from __future__ import annotations 

8 

9import io 

10from collections.abc import Generator, Iterable 

11from io import TextIOWrapper, BytesIO 

12from pathlib import Path 

13import re 

14from tokenize import open, detect_encoding 

15 

16cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)", re.UNICODE) 

17cookie_comment_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE) 

18 

19def source_to_unicode(txt: str | bytes | BytesIO, errors: str = 'replace', skip_encoding_cookie: bool = True) -> str: 

20 """Converts a bytes string with python source code to unicode. 

21 

22 Unicode strings are passed through unchanged. Byte strings are checked 

23 for the python source file encoding cookie to determine encoding. 

24 txt can be either a bytes buffer or a string containing the source 

25 code. 

26 """ 

27 if isinstance(txt, str): 

28 return txt 

29 if isinstance(txt, bytes): 

30 buffer = BytesIO(txt) 

31 else: 

32 buffer = txt 

33 try: 

34 encoding, _ = detect_encoding(buffer.readline) 

35 except SyntaxError: 

36 encoding = "ascii" 

37 buffer.seek(0) 

38 with TextIOWrapper(buffer, encoding, errors=errors, line_buffering=True) as text: 

39 text.mode = 'r' 

40 if skip_encoding_cookie: 

41 return u"".join(strip_encoding_cookie(text)) 

42 else: 

43 return text.read() 

44 

45def strip_encoding_cookie(filelike: Iterable[str]) -> Generator[str, None, None]: 

46 """Generator to pull lines from a text-mode file, skipping the encoding 

47 cookie if it is found in the first two lines. 

48 """ 

49 it = iter(filelike) 

50 try: 

51 first = next(it) 

52 if not cookie_comment_re.match(first): 

53 yield first 

54 second = next(it) 

55 if not cookie_comment_re.match(second): 

56 yield second 

57 except StopIteration: 

58 return 

59 

60 yield from it 

61 

62def read_py_file(filename: str | Path, skip_encoding_cookie: bool = True) -> str: 

63 """Read a Python file, using the encoding declared inside the file. 

64 

65 Parameters 

66 ---------- 

67 filename : str 

68 The path to the file to read. 

69 skip_encoding_cookie : bool 

70 If True (the default), and the encoding declaration is found in the first 

71 two lines, that line will be excluded from the output. 

72 

73 Returns 

74 ------- 

75 A unicode string containing the contents of the file. 

76 """ 

77 filepath = Path(filename) 

78 with open(filepath) as f: # the open function defined in this module. 

79 if skip_encoding_cookie: 

80 return "".join(strip_encoding_cookie(f)) 

81 else: 

82 return f.read() 

83 

84def read_py_url(url: str, errors: str = 'replace', skip_encoding_cookie: bool = True) -> str: 

85 """Read a Python file from a URL, using the encoding declared inside the file. 

86 

87 Parameters 

88 ---------- 

89 url : str 

90 The URL from which to fetch the file. 

91 errors : str 

92 How to handle decoding errors in the file. Options are the same as for 

93 bytes.decode(), but here 'replace' is the default. 

94 skip_encoding_cookie : bool 

95 If True (the default), and the encoding declaration is found in the first 

96 two lines, that line will be excluded from the output. 

97 

98 Returns 

99 ------- 

100 A unicode string containing the contents of the file. 

101 """ 

102 # Deferred import for faster start 

103 from urllib.request import urlopen 

104 response = urlopen(url) 

105 buffer = io.BytesIO(response.read()) 

106 return source_to_unicode(buffer, errors, skip_encoding_cookie)