##// END OF EJS Templates
Use pathlib in packaging.py, io.py, and gen_latex_symbols.py...
Justin Palmer -
Show More
@@ -1,104 +1,105 b''
1 1 """Implementation of packaging-related magic functions.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2018 The IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 import re
12 12 import shlex
13 13 import sys
14 from Pathlib import Path
14 15
15 16 from pathlib import Path
16 17 from IPython.core.magic import Magics, magics_class, line_magic
17 18
18 19
19 20 def _is_conda_environment():
20 21 """Return True if the current Python executable is in a conda env"""
21 22 # TODO: does this need to change on windows?
22 23 return Path(sys.prefix, "conda-meta", "history").exists()
23 24
24 25
25 26 def _get_conda_executable():
26 27 """Find the path to the conda executable"""
27 28 # Check if there is a conda executable in the same directory as the Python executable.
28 29 # This is the case within conda's root environment.
29 30 conda = Path(sys.executable).parent / "conda"
30 31 if conda.isfile():
31 32 return str(conda)
32 33
33 34 # Otherwise, attempt to extract the executable from conda history.
34 35 # This applies in any conda environment.
35 36 history = Path(sys.prefix, "conda-meta", "history").read_text()
36 37 match = re.search(
37 38 r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
38 39 history,
39 40 flags=re.MULTILINE,
40 41 )
41 42 if match:
42 43 return match.groupdict()["command"]
43 44
44 45 # Fallback: assume conda is available on the system path.
45 46 return "conda"
46 47
47 48
48 49 CONDA_COMMANDS_REQUIRING_PREFIX = {
49 50 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
50 51 }
51 52 CONDA_COMMANDS_REQUIRING_YES = {
52 53 'install', 'remove', 'uninstall', 'update', 'upgrade',
53 54 }
54 55 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
55 56 CONDA_YES_FLAGS = {'-y', '--y'}
56 57
57 58
58 59 @magics_class
59 60 class PackagingMagics(Magics):
60 61 """Magics related to packaging & installation"""
61 62
62 63 @line_magic
63 64 def pip(self, line):
64 65 """Run the pip package manager within the current kernel.
65 66
66 67 Usage:
67 68 %pip install [pkgs]
68 69 """
69 70 self.shell.system(' '.join([sys.executable, '-m', 'pip', line]))
70 71 print("Note: you may need to restart the kernel to use updated packages.")
71 72
72 73 @line_magic
73 74 def conda(self, line):
74 75 """Run the conda package manager within the current kernel.
75 76
76 77 Usage:
77 78 %conda install [pkgs]
78 79 """
79 80 if not _is_conda_environment():
80 81 raise ValueError("The python kernel does not appear to be a conda environment. "
81 82 "Please use ``%pip install`` instead.")
82 83
83 84 conda = _get_conda_executable()
84 85 args = shlex.split(line)
85 86 command = args[0]
86 87 args = args[1:]
87 88 extra_args = []
88 89
89 90 # When the subprocess does not allow us to respond "yes" during the installation,
90 91 # we need to insert --yes in the argument list for some commands
91 92 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
92 93 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
93 94 has_yes = set(args).intersection(CONDA_YES_FLAGS)
94 95 if stdin_disabled and needs_yes and not has_yes:
95 96 extra_args.append("--yes")
96 97
97 98 # Add --prefix to point conda installation to the current environment
98 99 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
99 100 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
100 101 if needs_prefix and not has_prefix:
101 102 extra_args.extend(["--prefix", sys.prefix])
102 103
103 104 self.shell.system(' '.join([conda, command] + extra_args + args))
104 105 print("\nNote: you may need to restart the kernel to use updated packages.")
@@ -1,248 +1,249 b''
1 1 # encoding: utf-8
2 2 """
3 3 IO related utilities.
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
11 11 import atexit
12 12 import os
13 13 import sys
14 14 import tempfile
15 15 import warnings
16 16 from warnings import warn
17 from pathlib import Path
17 18
18 19 from IPython.utils.decorators import undoc
19 20 from .capture import CapturedIO, capture_output
20 21
21 22 @undoc
22 23 class IOStream:
23 24
24 25 def __init__(self, stream, fallback=None):
25 26 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
26 27 DeprecationWarning, stacklevel=2)
27 28 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 29 if fallback is not None:
29 30 stream = fallback
30 31 else:
31 32 raise ValueError("fallback required, but not specified")
32 33 self.stream = stream
33 34 self._swrite = stream.write
34 35
35 36 # clone all methods not overridden:
36 37 def clone(meth):
37 38 return not hasattr(self, meth) and not meth.startswith('_')
38 39 for meth in filter(clone, dir(stream)):
39 40 try:
40 41 val = getattr(stream, meth)
41 42 except AttributeError:
42 43 pass
43 44 else:
44 45 setattr(self, meth, val)
45 46
46 47 def __repr__(self):
47 48 cls = self.__class__
48 49 tpl = '{mod}.{cls}({args})'
49 50 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
50 51
51 52 def write(self,data):
52 53 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
53 54 DeprecationWarning, stacklevel=2)
54 55 try:
55 56 self._swrite(data)
56 57 except:
57 58 try:
58 59 # print handles some unicode issues which may trip a plain
59 60 # write() call. Emulate write() by using an empty end
60 61 # argument.
61 62 print(data, end='', file=self.stream)
62 63 except:
63 64 # if we get here, something is seriously broken.
64 65 print('ERROR - failed to write data to stream:', self.stream,
65 66 file=sys.stderr)
66 67
67 68 def writelines(self, lines):
68 69 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
69 70 DeprecationWarning, stacklevel=2)
70 71 if isinstance(lines, str):
71 72 lines = [lines]
72 73 for line in lines:
73 74 self.write(line)
74 75
75 76 # This class used to have a writeln method, but regular files and streams
76 77 # in Python don't have this method. We need to keep this completely
77 78 # compatible so we removed it.
78 79
79 80 @property
80 81 def closed(self):
81 82 return self.stream.closed
82 83
83 84 def close(self):
84 85 pass
85 86
86 87 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
87 88 devnull = open(os.devnull, 'w')
88 89 atexit.register(devnull.close)
89 90
90 91 # io.std* are deprecated, but don't show our own deprecation warnings
91 92 # during initialization of the deprecated API.
92 93 with warnings.catch_warnings():
93 94 warnings.simplefilter('ignore', DeprecationWarning)
94 95 stdin = IOStream(sys.stdin, fallback=devnull)
95 96 stdout = IOStream(sys.stdout, fallback=devnull)
96 97 stderr = IOStream(sys.stderr, fallback=devnull)
97 98
98 99 class Tee(object):
99 100 """A class to duplicate an output stream to stdout/err.
100 101
101 102 This works in a manner very similar to the Unix 'tee' command.
102 103
103 104 When the object is closed or deleted, it closes the original file given to
104 105 it for duplication.
105 106 """
106 107 # Inspired by:
107 108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108 109
109 110 def __init__(self, file_or_name, mode="w", channel='stdout'):
110 111 """Construct a new Tee object.
111 112
112 113 Parameters
113 114 ----------
114 115 file_or_name : filename or open filehandle (writable)
115 116 File that will be duplicated
116 117
117 118 mode : optional, valid mode for open().
118 119 If a filename was give, open with this mode.
119 120
120 121 channel : str, one of ['stdout', 'stderr']
121 122 """
122 123 if channel not in ['stdout', 'stderr']:
123 124 raise ValueError('Invalid channel spec %s' % channel)
124 125
125 126 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
126 127 self.file = file_or_name
127 128 else:
128 129 self.file = open(file_or_name, mode)
129 130 self.channel = channel
130 131 self.ostream = getattr(sys, channel)
131 132 setattr(sys, channel, self)
132 133 self._closed = False
133 134
134 135 def close(self):
135 136 """Close the file and restore the channel."""
136 137 self.flush()
137 138 setattr(sys, self.channel, self.ostream)
138 139 self.file.close()
139 140 self._closed = True
140 141
141 142 def write(self, data):
142 143 """Write data to both channels."""
143 144 self.file.write(data)
144 145 self.ostream.write(data)
145 146 self.ostream.flush()
146 147
147 148 def flush(self):
148 149 """Flush both channels."""
149 150 self.file.flush()
150 151 self.ostream.flush()
151 152
152 153 def __del__(self):
153 154 if not self._closed:
154 155 self.close()
155 156
156 157
157 158 def ask_yes_no(prompt, default=None, interrupt=None):
158 159 """Asks a question and returns a boolean (y/n) answer.
159 160
160 161 If default is given (one of 'y','n'), it is used if the user input is
161 162 empty. If interrupt is given (one of 'y','n'), it is used if the user
162 163 presses Ctrl-C. Otherwise the question is repeated until an answer is
163 164 given.
164 165
165 166 An EOF is treated as the default answer. If there is no default, an
166 167 exception is raised to prevent infinite loops.
167 168
168 169 Valid answers are: y/yes/n/no (match is not case sensitive)."""
169 170
170 171 answers = {'y':True,'n':False,'yes':True,'no':False}
171 172 ans = None
172 173 while ans not in answers.keys():
173 174 try:
174 175 ans = input(prompt+' ').lower()
175 176 if not ans: # response was an empty string
176 177 ans = default
177 178 except KeyboardInterrupt:
178 179 if interrupt:
179 180 ans = interrupt
180 181 print("\r")
181 182 except EOFError:
182 183 if default in answers.keys():
183 184 ans = default
184 185 print()
185 186 else:
186 187 raise
187 188
188 189 return answers[ans]
189 190
190 191
191 192 def temp_pyfile(src, ext='.py'):
192 193 """Make a temporary python file, return filename and filehandle.
193 194
194 195 Parameters
195 196 ----------
196 197 src : string or list of strings (no need for ending newlines if list)
197 198 Source code to be written to the file.
198 199
199 200 ext : optional, string
200 201 Extension for the generated file.
201 202
202 203 Returns
203 204 -------
204 205 (filename, open filehandle)
205 206 It is the caller's responsibility to close the open file and unlink it.
206 207 """
207 fname = tempfile.mkstemp(ext)[1]
208 with open(fname,'w') as f:
208 fname = Path(tempfile.mkstemp(ext)[1])
209 with fname.open('w') as f:
209 210 f.write(src)
210 211 f.flush()
211 212 return fname
212 213
213 214 @undoc
214 215 def atomic_writing(*args, **kwargs):
215 216 """DEPRECATED: moved to notebook.services.contents.fileio"""
216 217 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
217 218 from notebook.services.contents.fileio import atomic_writing
218 219 return atomic_writing(*args, **kwargs)
219 220
220 221 @undoc
221 222 def raw_print(*args, **kw):
222 223 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
223 224 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
224 225
225 226 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
226 227 file=sys.__stdout__)
227 228 sys.__stdout__.flush()
228 229
229 230 @undoc
230 231 def raw_print_err(*args, **kw):
231 232 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
232 233 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
233 234
234 235 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
235 236 file=sys.__stderr__)
236 237 sys.__stderr__.flush()
237 238
238 239 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
239 240 # Keep for a version or two then should remove
240 241 rprint = raw_print
241 242 rprinte = raw_print_err
242 243
243 244 @undoc
244 245 def unicode_std_stream(stream='stdout'):
245 246 """DEPRECATED, moved to nbconvert.utils.io"""
246 247 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
247 248 from nbconvert.utils.io import unicode_std_stream
248 249 return unicode_std_stream(stream)
@@ -1,88 +1,87 b''
1 1 # coding: utf-8
2 2
3 3 # This script autogenerates `IPython.core.latex_symbols.py`, which contains a
4 4 # single dict , named `latex_symbols`. The keys in this dict are latex symbols,
5 5 # such as `\\alpha` and the values in the dict are the unicode equivalents for
6 6 # those. Most importantly, only unicode symbols that are valid identifiers in
7 7 # Python 3 are included.
8 8
9 9 #
10 10 # The original mapping of latex symbols to unicode comes from the `latex_symbols.jl` files from Julia.
11 11
12 import os, sys
12 from pathlib import Path
13 13
14 14 # Import the Julia LaTeX symbols
15 15 print('Importing latex_symbols.js from Julia...')
16 16 import requests
17 17 url = 'https://raw.githubusercontent.com/JuliaLang/julia/master/stdlib/REPL/src/latex_symbols.jl'
18 18 r = requests.get(url)
19 19
20 20
21 21 # Build a list of key, value pairs
22 22 print('Building a list of (latex, unicode) key-value pairs...')
23 23 lines = r.text.splitlines()
24 24
25 25 prefixes_line = lines.index('# "font" prefixes')
26 26 symbols_line = lines.index('# manual additions:')
27 27
28 28 prefix_dict = {}
29 29 for l in lines[prefixes_line + 1: symbols_line]:
30 30 p = l.split()
31 31 if not p or p[1] == 'latex_symbols': continue
32 32 prefix_dict[p[1]] = p[3]
33 33
34 34 idents = []
35 35 for l in lines[symbols_line:]:
36 36 if not '=>' in l: continue # if it's not a def, skip
37 37 if '#' in l: l = l[:l.index('#')] # get rid of eol comments
38 38 x, y = l.strip().split('=>')
39 39 if '*' in x: # if a prefix is present substitute it with its value
40 40 p, x = x.split('*')
41 41 x = prefix_dict[p][:-1] + x[1:]
42 42 x, y = x.split('"')[1], y.split('"')[1] # get the values in quotes
43 43 idents.append((x, y))
44 44
45 45 # Filter out non-valid identifiers
46 46 print('Filtering out characters that are not valid Python 3 identifiers')
47 47
48 48 def test_ident(i):
49 49 """Is the unicode string valid in a Python 3 identifier."""
50 50 # Some characters are not valid at the start of a name, but we still want to
51 51 # include them. So prefix with 'a', which is valid at the start.
52 52 return ('a' + i).isidentifier()
53 53
54 54 assert test_ident("α")
55 55 assert not test_ident('‴')
56 56
57 57 valid_idents = [line for line in idents if test_ident(line[1])]
58 58
59 59 # Write the `latex_symbols.py` module in the cwd
60 60
61 61 s = """# encoding: utf-8
62 62
63 63 # DO NOT EDIT THIS FILE BY HAND.
64 64
65 65 # To update this file, run the script /tools/gen_latex_symbols.py using Python 3
66 66
67 67 # This file is autogenerated from the file:
68 68 # https://raw.githubusercontent.com/JuliaLang/julia/master/base/latex_symbols.jl
69 69 # This original list is filtered to remove any unicode characters that are not valid
70 70 # Python identifiers.
71 71
72 72 latex_symbols = {\n
73 73 """
74 74 for line in valid_idents:
75 75 s += ' "%s" : "%s",\n' % (line[0], line[1])
76 76 s += "}\n"
77 77
78 78 s += """
79 79
80 80 reverse_latex_symbol = { v:k for k,v in latex_symbols.items()}
81 81 """
82 82
83 fn = os.path.join('..','IPython','core','latex_symbols.py')
84 print("Writing the file: %s" % fn)
85 with open(fn, 'w', encoding='utf-8') as f:
86 f.write(s)
83 fn = Path('..', 'IPython', 'core', 'latex_symbols.py')
84 print("Writing the file: %s" % str(fn))
85 fn.write_text(s, encoding='utf-8')
87 86
88 87
General Comments 0
You need to be logged in to leave comments. Login now