passagemath-environment 10.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_environment-10.4.1.data/scripts/sage +1140 -0
- passagemath_environment-10.4.1.data/scripts/sage-env +667 -0
- passagemath_environment-10.4.1.data/scripts/sage-num-threads.py +105 -0
- passagemath_environment-10.4.1.data/scripts/sage-python +2 -0
- passagemath_environment-10.4.1.data/scripts/sage-venv-config +42 -0
- passagemath_environment-10.4.1.data/scripts/sage-version.sh +9 -0
- passagemath_environment-10.4.1.dist-info/METADATA +76 -0
- passagemath_environment-10.4.1.dist-info/RECORD +70 -0
- passagemath_environment-10.4.1.dist-info/WHEEL +5 -0
- passagemath_environment-10.4.1.dist-info/top_level.txt +1 -0
- sage/all__sagemath_environment.py +4 -0
- sage/env.py +496 -0
- sage/features/__init__.py +981 -0
- sage/features/all.py +126 -0
- sage/features/bliss.py +85 -0
- sage/features/cddlib.py +38 -0
- sage/features/coxeter3.py +45 -0
- sage/features/csdp.py +83 -0
- sage/features/cython.py +38 -0
- sage/features/databases.py +302 -0
- sage/features/dvipng.py +40 -0
- sage/features/ecm.py +42 -0
- sage/features/ffmpeg.py +119 -0
- sage/features/four_ti_2.py +55 -0
- sage/features/fricas.py +66 -0
- sage/features/gap.py +86 -0
- sage/features/gfan.py +38 -0
- sage/features/giac.py +30 -0
- sage/features/graph_generators.py +171 -0
- sage/features/graphviz.py +117 -0
- sage/features/igraph.py +44 -0
- sage/features/imagemagick.py +138 -0
- sage/features/interfaces.py +256 -0
- sage/features/internet.py +65 -0
- sage/features/jmol.py +44 -0
- sage/features/join_feature.py +146 -0
- sage/features/kenzo.py +77 -0
- sage/features/latex.py +300 -0
- sage/features/latte.py +85 -0
- sage/features/lrs.py +164 -0
- sage/features/mcqd.py +45 -0
- sage/features/meataxe.py +46 -0
- sage/features/mip_backends.py +114 -0
- sage/features/msolve.py +68 -0
- sage/features/nauty.py +70 -0
- sage/features/normaliz.py +43 -0
- sage/features/palp.py +65 -0
- sage/features/pandoc.py +42 -0
- sage/features/pdf2svg.py +41 -0
- sage/features/phitigra.py +42 -0
- sage/features/pkg_systems.py +195 -0
- sage/features/polymake.py +43 -0
- sage/features/poppler.py +58 -0
- sage/features/rubiks.py +180 -0
- sage/features/sagemath.py +1205 -0
- sage/features/sat.py +103 -0
- sage/features/singular.py +48 -0
- sage/features/sirocco.py +45 -0
- sage/features/sphinx.py +71 -0
- sage/features/standard.py +38 -0
- sage/features/symengine_py.py +44 -0
- sage/features/tdlib.py +38 -0
- sage/features/threejs.py +75 -0
- sage/features/topcom.py +67 -0
- sage/misc/all__sagemath_environment.py +2 -0
- sage/misc/package.py +570 -0
- sage/misc/package_dir.py +621 -0
- sage/misc/temporary_file.py +546 -0
- sage/misc/viewer.py +369 -0
- sage/version.py +5 -0
@@ -0,0 +1,546 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-environment
|
2
|
+
"""
|
3
|
+
Temporary file handling
|
4
|
+
|
5
|
+
AUTHORS:
|
6
|
+
|
7
|
+
- Volker Braun, Jeroen Demeyer (2012-10-18): move these functions here
|
8
|
+
from sage/misc/misc.py and make them secure, see :issue:`13579`.
|
9
|
+
|
10
|
+
- Jeroen Demeyer (2013-03-17): add :class:`atomic_write`,
|
11
|
+
see :issue:`14292`.
|
12
|
+
|
13
|
+
- Sebastian Oehms (2021-08-07): add :class:`atomic_dir`,
|
14
|
+
see :issue:`32344`
|
15
|
+
"""
|
16
|
+
# ****************************************************************************
|
17
|
+
# Copyright (C) 2012 Volker Braun <vbraun@stp.dias.ie>
|
18
|
+
# Copyright (C) 2012 Jeroen Demeyer <jdemeyer@cage.ugent.be>
|
19
|
+
#
|
20
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
21
|
+
# as published by the Free Software Foundation; either version 2 of
|
22
|
+
# the License, or (at your option) any later version.
|
23
|
+
# https://www.gnu.org/licenses/
|
24
|
+
# ****************************************************************************
|
25
|
+
|
26
|
+
import atexit
|
27
|
+
import os
|
28
|
+
import tempfile
|
29
|
+
from typing import IO
|
30
|
+
|
31
|
+
# Until tmp_dir() and tmp_filename() are removed, we use this directory
|
32
|
+
# as the parent for all temporary files & directories created by them.
|
33
|
+
# This lets us clean up after those two functions when sage exits normally
|
34
|
+
# using an atexit hook
|
35
|
+
TMP_DIR_FILENAME_BASE = tempfile.TemporaryDirectory()
|
36
|
+
atexit.register(lambda: TMP_DIR_FILENAME_BASE.cleanup())
|
37
|
+
|
38
|
+
|
39
|
+
#################################################################
|
40
|
+
# temporary directory
|
41
|
+
#################################################################
|
42
|
+
|
43
|
+
def tmp_dir(name='dir_', ext='') -> str:
|
44
|
+
r"""
|
45
|
+
Create and return a temporary directory in
|
46
|
+
``$HOME/.sage/temp/hostname/pid/``
|
47
|
+
|
48
|
+
The temporary directory is deleted automatically when Sage exits.
|
49
|
+
|
50
|
+
INPUT:
|
51
|
+
|
52
|
+
- ``name`` -- (default: ``'dir_'``) a prefix for the directory name
|
53
|
+
|
54
|
+
- ``ext`` -- (default: ``''``) a suffix for the directory name
|
55
|
+
|
56
|
+
OUTPUT:
|
57
|
+
|
58
|
+
The absolute path of the temporary directory created, with a
|
59
|
+
trailing slash (or whatever the path separator is on your OS).
|
60
|
+
|
61
|
+
EXAMPLES::
|
62
|
+
|
63
|
+
sage: d = tmp_dir('dir_testing_', '.extension')
|
64
|
+
sage: d # random output
|
65
|
+
'/home/username/.sage/temp/hostname/7961/dir_testing_XgRu4p.extension/'
|
66
|
+
sage: os.chdir(d)
|
67
|
+
sage: f = open('file_inside_d', 'w')
|
68
|
+
|
69
|
+
Temporary directories are unaccessible by other users::
|
70
|
+
|
71
|
+
sage: os.stat(d).st_mode & 0o077
|
72
|
+
0
|
73
|
+
sage: f.close()
|
74
|
+
"""
|
75
|
+
tmp = tempfile.mkdtemp(prefix=name,
|
76
|
+
suffix=ext,
|
77
|
+
dir=TMP_DIR_FILENAME_BASE.name)
|
78
|
+
name = os.path.abspath(tmp)
|
79
|
+
return name + os.sep
|
80
|
+
|
81
|
+
|
82
|
+
#################################################################
|
83
|
+
# temporary filename
|
84
|
+
#################################################################
|
85
|
+
|
86
|
+
def tmp_filename(name='tmp_', ext='') -> str:
|
87
|
+
r"""
|
88
|
+
Create and return a temporary file in
|
89
|
+
``$HOME/.sage/temp/hostname/pid/``
|
90
|
+
|
91
|
+
The temporary file is deleted automatically when Sage exits.
|
92
|
+
|
93
|
+
.. warning::
|
94
|
+
|
95
|
+
If you need a particular file extension always use
|
96
|
+
``tmp_filename(ext='.foo')``, this will ensure that the file
|
97
|
+
does not yet exist. If you were to use
|
98
|
+
``tmp_filename()+".foo"``, then you might overwrite an
|
99
|
+
existing file!
|
100
|
+
|
101
|
+
INPUT:
|
102
|
+
|
103
|
+
- ``name`` -- (default: ``'tmp_'``) a prefix for the file name
|
104
|
+
|
105
|
+
- ``ext`` -- (default: ``''``) a suffix for the file name. If you
|
106
|
+
want a filename extension in the usual sense, this should start
|
107
|
+
with a dot.
|
108
|
+
|
109
|
+
OUTPUT: the absolute path of the temporary file created
|
110
|
+
|
111
|
+
EXAMPLES::
|
112
|
+
|
113
|
+
sage: fn = tmp_filename('just_for_testing_', '.extension')
|
114
|
+
sage: fn # random
|
115
|
+
'/home/username/.sage/temp/hostname/8044/just_for_testing_tVVHsn.extension'
|
116
|
+
sage: f = open(fn, 'w')
|
117
|
+
|
118
|
+
Temporary files are unaccessible by other users::
|
119
|
+
|
120
|
+
sage: os.stat(fn).st_mode & 0o077
|
121
|
+
0
|
122
|
+
sage: f.close()
|
123
|
+
"""
|
124
|
+
handle, tmp = tempfile.mkstemp(prefix=name,
|
125
|
+
suffix=ext,
|
126
|
+
dir=TMP_DIR_FILENAME_BASE.name)
|
127
|
+
os.close(handle)
|
128
|
+
name = os.path.abspath(tmp)
|
129
|
+
return name
|
130
|
+
|
131
|
+
|
132
|
+
#################################################################
|
133
|
+
# write to a temporary file and move it in place
|
134
|
+
#################################################################
|
135
|
+
class atomic_write:
|
136
|
+
"""
|
137
|
+
Write to a given file using a temporary file and then rename it
|
138
|
+
to the target file. This renaming should be atomic on modern
|
139
|
+
operating systems. Therefore, this class can be used to avoid race
|
140
|
+
conditions when a file might be read while it is being written.
|
141
|
+
It also avoids having partially written files due to exceptions
|
142
|
+
or crashes.
|
143
|
+
|
144
|
+
This is to be used in a ``with`` statement, where a temporary file
|
145
|
+
is created when entering the ``with`` and is moved in place of the
|
146
|
+
target file when exiting the ``with`` (if no exceptions occurred).
|
147
|
+
|
148
|
+
INPUT:
|
149
|
+
|
150
|
+
- ``target_filename`` -- the name of the file to be written
|
151
|
+
Normally, the contents of this file will be overwritten
|
152
|
+
|
153
|
+
- ``append`` -- boolean (default: ``False``); if ``True`` and
|
154
|
+
``target_filename`` is an existing file, then copy the current
|
155
|
+
contents of ``target_filename`` to the temporary file when
|
156
|
+
entering the ``with`` statement. Otherwise, the temporary file is
|
157
|
+
initially empty.
|
158
|
+
|
159
|
+
- ``mode`` -- (default: ``0o666``) mode bits for the file. The
|
160
|
+
temporary file is created with mode ``mode & ~umask`` and the
|
161
|
+
resulting file will also have these permissions (unless the
|
162
|
+
mode bits of the file were changed manually). (Not to be confused with
|
163
|
+
the file opening mode.)
|
164
|
+
|
165
|
+
- ``binary`` -- boolean (default: ``False``);
|
166
|
+
the underlying file is opened in binary mode. If ``False`` then it is
|
167
|
+
opened in text mode and an encoding with which to write the file may be
|
168
|
+
supplied.
|
169
|
+
|
170
|
+
- ``**kwargs`` -- additional keyword arguments passed to the underlying
|
171
|
+
`io.open` call
|
172
|
+
|
173
|
+
EXAMPLES::
|
174
|
+
|
175
|
+
sage: from sage.misc.temporary_file import atomic_write
|
176
|
+
sage: target_file = tmp_filename()
|
177
|
+
sage: with open(target_file, 'w') as f:
|
178
|
+
....: _ = f.write("Old contents")
|
179
|
+
sage: with atomic_write(target_file) as f:
|
180
|
+
....: _ = f.write("New contents")
|
181
|
+
....: f.flush()
|
182
|
+
....: with open(target_file, 'r') as f2:
|
183
|
+
....: f2.read()
|
184
|
+
'Old contents'
|
185
|
+
sage: with open(target_file, 'r') as f:
|
186
|
+
....: f.read()
|
187
|
+
'New contents'
|
188
|
+
|
189
|
+
The name of the temporary file can be accessed using ``f.name``.
|
190
|
+
It is not a problem to close and re-open the temporary file::
|
191
|
+
|
192
|
+
sage: from sage.misc.temporary_file import atomic_write
|
193
|
+
sage: target_file = tmp_filename()
|
194
|
+
sage: with open(target_file, 'w') as f:
|
195
|
+
....: _ = f.write("Old contents")
|
196
|
+
sage: with atomic_write(target_file) as f:
|
197
|
+
....: f.close()
|
198
|
+
....: with open(f.name, 'w') as f2:
|
199
|
+
....: _ = f2.write("Newer contents")
|
200
|
+
sage: with open(target_file, 'r') as f:
|
201
|
+
....: f.read()
|
202
|
+
'Newer contents'
|
203
|
+
|
204
|
+
If an exception occurs while writing the file, the target file is
|
205
|
+
not touched::
|
206
|
+
|
207
|
+
sage: with atomic_write(target_file) as f:
|
208
|
+
....: _ = f.write("Newest contents")
|
209
|
+
....: raise RuntimeError
|
210
|
+
Traceback (most recent call last):
|
211
|
+
...
|
212
|
+
RuntimeError
|
213
|
+
sage: with open(target_file, 'r') as f:
|
214
|
+
....: f.read()
|
215
|
+
'Newer contents'
|
216
|
+
|
217
|
+
Some examples of using the ``append`` option. Note that the file
|
218
|
+
is never opened in "append" mode, it is possible to overwrite
|
219
|
+
existing data::
|
220
|
+
|
221
|
+
sage: target_file = tmp_filename()
|
222
|
+
sage: with atomic_write(target_file, append=True) as f:
|
223
|
+
....: _ = f.write("Hello")
|
224
|
+
sage: with atomic_write(target_file, append=True) as f:
|
225
|
+
....: _ = f.write(" World")
|
226
|
+
sage: with open(target_file, 'r') as f:
|
227
|
+
....: f.read()
|
228
|
+
'Hello World'
|
229
|
+
sage: with atomic_write(target_file, append=True) as f:
|
230
|
+
....: _ = f.seek(0)
|
231
|
+
....: _ = f.write("HELLO")
|
232
|
+
sage: with open(target_file, 'r') as f:
|
233
|
+
....: f.read()
|
234
|
+
'HELLO World'
|
235
|
+
|
236
|
+
If the target file is a symbolic link, the link is kept and the
|
237
|
+
target of the link is written to::
|
238
|
+
|
239
|
+
sage: link_to_target = os.path.join(tmp_dir(), "templink")
|
240
|
+
sage: os.symlink(target_file, link_to_target)
|
241
|
+
sage: with atomic_write(link_to_target) as f:
|
242
|
+
....: _ = f.write("Newest contents")
|
243
|
+
sage: with open(target_file, 'r') as f:
|
244
|
+
....: f.read()
|
245
|
+
'Newest contents'
|
246
|
+
|
247
|
+
We check the permission bits of the new file. Note that the old
|
248
|
+
permissions do not matter::
|
249
|
+
|
250
|
+
sage: os.chmod(target_file, 0o600)
|
251
|
+
sage: _ = os.umask(0o022)
|
252
|
+
sage: with atomic_write(target_file) as f:
|
253
|
+
....: pass
|
254
|
+
sage: '{:#o}'.format(os.stat(target_file).st_mode & 0o777)
|
255
|
+
'0o644'
|
256
|
+
sage: _ = os.umask(0o077)
|
257
|
+
sage: with atomic_write(target_file, mode=0o777) as f:
|
258
|
+
....: pass
|
259
|
+
sage: '{:#o}'.format(os.stat(target_file).st_mode & 0o777)
|
260
|
+
'0o700'
|
261
|
+
|
262
|
+
Test writing twice to the same target file. The outermost ``with``
|
263
|
+
"wins"::
|
264
|
+
|
265
|
+
sage: with open(target_file, 'w') as f:
|
266
|
+
....: _ = f.write('>>> ')
|
267
|
+
sage: with atomic_write(target_file, append=True) as f, \
|
268
|
+
....: atomic_write(target_file, append=True) as g:
|
269
|
+
....: _ = f.write("AAA"); f.close()
|
270
|
+
....: _ = g.write("BBB"); g.close()
|
271
|
+
sage: with open(target_file, 'r') as f:
|
272
|
+
....: f.read()
|
273
|
+
'>>> AAA'
|
274
|
+
|
275
|
+
Supplying an encoding means we're writing the file in "text mode" (in the
|
276
|
+
same sense as `io.open`) and so we must write unicode strings::
|
277
|
+
|
278
|
+
sage: target_file = tmp_filename()
|
279
|
+
sage: with atomic_write(target_file, binary=False,
|
280
|
+
....: encoding='utf-8') as f:
|
281
|
+
....: _ = f.write(u'Hélas')
|
282
|
+
sage: import io
|
283
|
+
sage: with io.open(target_file, encoding='utf-8') as f:
|
284
|
+
....: print(f.read())
|
285
|
+
Hélas
|
286
|
+
|
287
|
+
Supplying an encoding in binary mode (or other arguments that don't
|
288
|
+
make sense to `io.open` in binary mode) is an error::
|
289
|
+
|
290
|
+
sage: writer = atomic_write(target_file, binary=True,
|
291
|
+
....: encoding='utf-8')
|
292
|
+
sage: with writer as f:
|
293
|
+
....: _ = f.write(u'Hello')
|
294
|
+
Traceback (most recent call last):
|
295
|
+
...
|
296
|
+
ValueError: binary mode doesn't take an encoding argument
|
297
|
+
sage: os.path.exists(writer.tempname)
|
298
|
+
False
|
299
|
+
"""
|
300
|
+
def __init__(self, target_filename, append=False, mode=0o666,
|
301
|
+
binary=False, **kwargs) -> None:
|
302
|
+
"""
|
303
|
+
TESTS::
|
304
|
+
|
305
|
+
sage: from sage.misc.temporary_file import atomic_write
|
306
|
+
sage: link_to_target = os.path.join(tmp_dir(), "templink")
|
307
|
+
sage: os.symlink("/foobar", link_to_target)
|
308
|
+
sage: aw = atomic_write(link_to_target)
|
309
|
+
sage: print(aw.target)
|
310
|
+
/foobar
|
311
|
+
sage: print(aw.tmpdir)
|
312
|
+
/
|
313
|
+
"""
|
314
|
+
self.target = os.path.realpath(target_filename)
|
315
|
+
self.tmpdir = os.path.dirname(self.target)
|
316
|
+
self.append = append
|
317
|
+
# Remove umask bits from mode
|
318
|
+
umask = os.umask(0)
|
319
|
+
os.umask(umask)
|
320
|
+
self.mode = mode & (~umask)
|
321
|
+
|
322
|
+
# 'text' mode is the default on Python 3
|
323
|
+
self.binary = binary
|
324
|
+
self.kwargs = kwargs
|
325
|
+
|
326
|
+
def __enter__(self) -> IO:
|
327
|
+
"""
|
328
|
+
Create and return a temporary file in ``self.tmpdir`` (normally
|
329
|
+
the same directory as the target file).
|
330
|
+
|
331
|
+
If ``self.append``, then copy the current contents of
|
332
|
+
``self.target`` to the temporary file.
|
333
|
+
|
334
|
+
OUTPUT: a file returned by :func:`tempfile.NamedTemporaryFile`
|
335
|
+
|
336
|
+
TESTS::
|
337
|
+
|
338
|
+
sage: from sage.misc.temporary_file import atomic_write
|
339
|
+
sage: aw = atomic_write(tmp_filename())
|
340
|
+
sage: with aw as f:
|
341
|
+
....: os.path.dirname(aw.target) == os.path.dirname(f.name)
|
342
|
+
True
|
343
|
+
"""
|
344
|
+
|
345
|
+
fd, name = tempfile.mkstemp(dir=self.tmpdir)
|
346
|
+
self.tempname = os.path.abspath(name)
|
347
|
+
|
348
|
+
rmode = 'r' + ('b' if self.binary else '')
|
349
|
+
wmode = 'w+' + ('b' if self.binary else '')
|
350
|
+
|
351
|
+
try:
|
352
|
+
self.tempfile = open(name, wmode, **self.kwargs)
|
353
|
+
except Exception:
|
354
|
+
# Some invalid arguments were passed to io.open
|
355
|
+
os.unlink(name)
|
356
|
+
raise
|
357
|
+
finally:
|
358
|
+
os.close(fd)
|
359
|
+
|
360
|
+
os.chmod(name, self.mode)
|
361
|
+
if self.append:
|
362
|
+
try:
|
363
|
+
with open(self.target, rmode, **self.kwargs) as f:
|
364
|
+
r = f.read()
|
365
|
+
except OSError:
|
366
|
+
pass
|
367
|
+
else:
|
368
|
+
self.tempfile.write(r)
|
369
|
+
|
370
|
+
return self.tempfile
|
371
|
+
|
372
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
373
|
+
"""
|
374
|
+
If the ``with`` block was successful, move the temporary file
|
375
|
+
to the target file. Otherwise, delete the temporary file.
|
376
|
+
|
377
|
+
TESTS:
|
378
|
+
|
379
|
+
Check that the temporary file is deleted if there was an
|
380
|
+
exception::
|
381
|
+
|
382
|
+
sage: from sage.misc.temporary_file import atomic_write
|
383
|
+
sage: with atomic_write(tmp_filename()) as f:
|
384
|
+
....: tempname = f.name
|
385
|
+
....: raise RuntimeError
|
386
|
+
Traceback (most recent call last):
|
387
|
+
...
|
388
|
+
RuntimeError
|
389
|
+
sage: os.path.exists(tempname)
|
390
|
+
False
|
391
|
+
"""
|
392
|
+
# Flush the file contents to disk (to be safe even if the
|
393
|
+
# system crashes) and close the file.
|
394
|
+
if not self.tempfile.closed:
|
395
|
+
self.tempfile.flush()
|
396
|
+
os.fsync(self.tempfile.fileno())
|
397
|
+
self.tempfile.close()
|
398
|
+
|
399
|
+
if exc_type is None:
|
400
|
+
# Success: move temporary file to target file
|
401
|
+
try:
|
402
|
+
os.rename(self.tempname, self.target)
|
403
|
+
except OSError:
|
404
|
+
os.unlink(self.target)
|
405
|
+
os.rename(self.tempname, self.target)
|
406
|
+
else:
|
407
|
+
# Failure: delete temporary file
|
408
|
+
os.unlink(self.tempname)
|
409
|
+
|
410
|
+
#################################################################
|
411
|
+
# write to a temporary directory and move it in place
|
412
|
+
#################################################################
|
413
|
+
|
414
|
+
|
415
|
+
class atomic_dir:
|
416
|
+
"""
|
417
|
+
Write to a given directory using a temporary directory and then rename it
|
418
|
+
to the target directory. This is for creating a directory whose contents
|
419
|
+
are determined uniquely by the directory name. If multiple threads or
|
420
|
+
processes attempt to create it in parallel, then it does not matter which
|
421
|
+
thread created it. Despite this assumption the contents of the directories
|
422
|
+
differ in the examples for demonstration purpose.
|
423
|
+
|
424
|
+
See also :class:`atomic_write`.
|
425
|
+
|
426
|
+
INPUT:
|
427
|
+
|
428
|
+
- ``target_directory`` -- the name of the directory to be written;
|
429
|
+
if it exists then the previous contents will be kept
|
430
|
+
|
431
|
+
EXAMPLES::
|
432
|
+
|
433
|
+
sage: from sage.misc.temporary_file import atomic_dir
|
434
|
+
sage: target_dir = tmp_dir()
|
435
|
+
sage: with atomic_dir(target_dir) as d:
|
436
|
+
....: target_file = os.path.join(d.name, 'test')
|
437
|
+
....: with open(target_file, 'w') as f:
|
438
|
+
....: _ = f.write("First")
|
439
|
+
....: f.flush()
|
440
|
+
....: with atomic_dir(target_dir) as e:
|
441
|
+
....: target_file2 = os.path.join(e.name, 'test')
|
442
|
+
....: with open(target_file2, 'w') as g:
|
443
|
+
....: _ = g.write("Second")
|
444
|
+
....: g.flush()
|
445
|
+
....: with open(target_file, 'r') as f:
|
446
|
+
....: f.read()
|
447
|
+
'First'
|
448
|
+
sage: with atomic_dir(target_dir) as d:
|
449
|
+
....: target_file = os.path.join(d.name, 'test')
|
450
|
+
....: with open(target_file, 'w') as f:
|
451
|
+
....: _ = f.write("Third")
|
452
|
+
sage: target = os.path.join(target_dir, 'test')
|
453
|
+
sage: with open(target, 'r') as h:
|
454
|
+
....: h.read()
|
455
|
+
'Second'
|
456
|
+
"""
|
457
|
+
def __init__(self, target_directory) -> None:
|
458
|
+
r"""
|
459
|
+
TESTS::
|
460
|
+
|
461
|
+
sage: from sage.misc.temporary_file import atomic_dir
|
462
|
+
sage: link_to_target = os.path.join(tmp_dir(), "templink")
|
463
|
+
sage: os.symlink("/foobar", link_to_target)
|
464
|
+
sage: aw = atomic_dir(link_to_target)
|
465
|
+
sage: print(aw.target)
|
466
|
+
/foobar
|
467
|
+
sage: print(aw.tmpdir)
|
468
|
+
/
|
469
|
+
"""
|
470
|
+
self.target = os.path.realpath(target_directory)
|
471
|
+
self.tmpdir = os.path.dirname(self.target)
|
472
|
+
|
473
|
+
def __enter__(self):
|
474
|
+
r"""
|
475
|
+
Create and return a temporary directory in ``self.tmpdir`` (normally
|
476
|
+
the same directory as the target file).
|
477
|
+
|
478
|
+
OUTPUT: a directory returned by :func:`tempfile.TemporaryDirectory`
|
479
|
+
|
480
|
+
TESTS::
|
481
|
+
|
482
|
+
sage: from sage.misc.temporary_file import atomic_dir
|
483
|
+
sage: aw = atomic_dir(tmp_dir())
|
484
|
+
sage: with aw as d:
|
485
|
+
....: os.path.dirname(aw.target) == os.path.dirname(d.name)
|
486
|
+
True
|
487
|
+
"""
|
488
|
+
tdir = tempfile.TemporaryDirectory(dir=self.tmpdir)
|
489
|
+
self.tempname = os.path.abspath(tdir.name)
|
490
|
+
return tdir
|
491
|
+
|
492
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
493
|
+
"""
|
494
|
+
If the ``with`` block was successful, move the temporary directory
|
495
|
+
to the target directory. Otherwise, delete the temporary directory.
|
496
|
+
|
497
|
+
TESTS:
|
498
|
+
|
499
|
+
Check that the temporary directory is deleted if there was an
|
500
|
+
exception::
|
501
|
+
|
502
|
+
sage: from sage.misc.temporary_file import atomic_dir
|
503
|
+
sage: with atomic_dir(tmp_dir()) as d:
|
504
|
+
....: tempname = d.name
|
505
|
+
....: raise RuntimeError
|
506
|
+
Traceback (most recent call last):
|
507
|
+
...
|
508
|
+
RuntimeError
|
509
|
+
sage: os.path.exists(tempname)
|
510
|
+
False
|
511
|
+
"""
|
512
|
+
import shutil
|
513
|
+
if exc_type is None:
|
514
|
+
# Success: move temporary file to target file
|
515
|
+
try:
|
516
|
+
os.rename(self.tempname, self.target)
|
517
|
+
except OSError:
|
518
|
+
# Race: Another thread or process must have created
|
519
|
+
# the directory
|
520
|
+
pass
|
521
|
+
else:
|
522
|
+
# Failure: delete temporary file
|
523
|
+
shutil.rmtree(self.tempname)
|
524
|
+
|
525
|
+
|
526
|
+
_spyx_tmp = None
|
527
|
+
|
528
|
+
|
529
|
+
def spyx_tmp() -> str:
|
530
|
+
r"""
|
531
|
+
The temporary directory used to store pyx files.
|
532
|
+
|
533
|
+
We cache the result of this function "by hand" so that the same
|
534
|
+
temporary directory will always be returned. A function is used to
|
535
|
+
delay creating a directory until (if) it is needed. The temporary
|
536
|
+
directory is removed when sage terminates by way of an atexit
|
537
|
+
hook.
|
538
|
+
"""
|
539
|
+
global _spyx_tmp
|
540
|
+
if _spyx_tmp:
|
541
|
+
return _spyx_tmp
|
542
|
+
|
543
|
+
d = tempfile.TemporaryDirectory()
|
544
|
+
_spyx_tmp = os.path.join(d.name, 'spyx')
|
545
|
+
atexit.register(lambda: d.cleanup())
|
546
|
+
return _spyx_tmp
|