Skip to content

Commit

Permalink
Trac #17654: Fix more load/save bugs
Browse files Browse the repository at this point in the history
1. Remove all documentation about `load filename`, it should be
`load("filename")`.
1. `src.sage.repl.load` calls `eval`(!) on the filename, deprecate that
behaviour.
1. The `optional - internet` tests in `src/sage/repl/load.py` are broken
since #17396.
1. Move loading of Fortran files to `src/sage/repl/load.py`.

URL: http://trac.sagemath.org/17654
Reported by: jdemeyer
Ticket author(s): Jeroen Demeyer
Reviewer(s): Marc Mezzarobba
  • Loading branch information
Release Manager authored and vbraun committed Feb 17, 2015
2 parents 322a54f + 1e8ee98 commit c23b064
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 123 deletions.
22 changes: 13 additions & 9 deletions src/sage/misc/remote_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@
def get_remote_file(filename, verbose=True):
"""
INPUT:
filename -- the URL of a file on the web, e.g.,
"http://modular.math.washington.edu/myfile.txt"
verbose -- whether to display download status
- ``filename`` -- the URL of a file on the web, e.g.,
``"http://modular.math.washington.edu/myfile.txt"``
- ``verbose`` -- whether to display download status
OUTPUT:
creates a file in the temp directory and returns the
absolute path to that file.
EXAMPLES:
creates a file in the temp directory and returns the absolute path
to that file.
EXAMPLES::
sage: g = get_remote_file("http://sagemath.org/ack.html", verbose=False) # optional - internet
sage: len(open(g).read()) # optional; randomly growing.
sage: len(open(g).read()) # optional - internet; random
10198
"""
if verbose:
print("Attempting to load remote file: " + filename)
import misc

temp_name = misc.tmp_filename() + '.' + os.path.splitext(filename)[1][1:]
from sage.misc.temporary_file import tmp_filename
temp_name = tmp_filename() + '.' + os.path.splitext(filename)[1][1:]
# IMPORTANT -- urllib takes a long time to load,
# so do not import it in the module scope.
import urllib
Expand Down
31 changes: 10 additions & 21 deletions src/sage/repl/attach.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def load_attach_path(path=None, replace=False):
sage: attach('test.py')
Traceback (most recent call last):
...
IOError: did not find file 'test.py' in load / attach search path
IOError: did not find file 'test.py' to load or attach
sage: load_attach_path(t_dir)
sage: attach('test.py')
111
Expand All @@ -178,7 +178,7 @@ def load_attach_path(path=None, replace=False):
sage: load('test.py')
Traceback (most recent call last):
...
IOError: did not find file 'test.py' in load / attach search path
IOError: did not find file 'test.py' to load or attach
The function returns a reference to the path list::
Expand Down Expand Up @@ -263,23 +263,11 @@ def attach(*files):
Attach a file or files to a running instance of Sage and also load
that file.
USAGE:
``attach file1 ...`` - space-separated list of ``.py``, ``.pyx``,
and ``.sage`` files, or ``attach('file1', 'file2')`` - filenames as
strings, given as arguments to :func:`attach`.
:meth:`~sage.repl.load.load` is the same as :func:`attach`, but
doesn't automatically reload a file when it changes.
.. NOTE::
INPUT:
On the Sage prompt you can also just type ``attach "foo.sage"``
as a short-hand for ``attach('foo.sage')``. However this
alternate form is not part of the Python language and does not
work in Python scripts.
- ``files`` -- a list of filenames (strings) to attach.
EFFECT:
OUTPUT:
Each file is read in and added to an internal list of watched files.
The meaning of reading in a file depends on the file type:
Expand All @@ -299,15 +287,16 @@ def attach(*files):
a command, the attached file will be re-read automatically (with no
intervention on your part).
.. SEEALSO::
:meth:`~sage.repl.load.load` is the same as :func:`attach`, but
doesn't automatically reload a file when it changes.
EXAMPLES:
You attach a file, e.g., ``foo.sage`` or ``foo.py`` or
``foo.pyx``, to a running Sage session by typing::
sage: attach foo.sage # or foo.py or foo.pyx or even a URL to such a file (not tested)
or::
sage: attach('foo.sage') # not tested
Here we test attaching multiple files at once::
Expand Down
140 changes: 82 additions & 58 deletions src/sage/repl/load.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Load Python, Sage, Cython and Magma files in Sage
Load Python, Sage, Cython, Fortran and Magma files in Sage
"""

#*****************************************************************************
Expand All @@ -20,8 +20,8 @@ def is_loadable_filename(filename):
"""
Returns whether a file can be loaded into Sage. This checks only
whether its name ends in one of the supported extensions ``.py``,
``.pyx``, ``.sage``, ``.spyx``, and ``.m``. Note: :func:`load`
assumes the latter signifies a Magma file.
``.pyx``, ``.sage``, ``.spyx``, ``.f``, ``.f90`` and ``.m``.
Note: :func:`load` assumes the latter signifies a Magma file.
INPUT:
Expand All @@ -39,12 +39,13 @@ def is_loadable_filename(filename):
False
sage: sage.repl.load.is_loadable_filename('foo.sage')
True
sage: sage.repl.load.is_loadable_filename('FOO.F90')
True
sage: sage.repl.load.is_loadable_filename('foo.m')
True
"""
if filename.endswith(('.py', '.pyx', '.sage', '.spyx', '.m')):
return True
return False
ext = os.path.splitext(filename)[1].lower()
return ext in ('.py', '.pyx', '.sage', '.spyx', '.f', '.f90', '.m')


def load_cython(name):
Expand All @@ -68,9 +69,8 @@ def load_cython(name):


def load(filename, globals, attach=False):
"""
Executes a file in the scope given by ``globals``. The
``filename`` itself is also evaluated in the scope. If the name
r"""
Executes a file in the scope given by ``globals``. If the name
starts with ``http://``, it is treated as a URL and downloaded.
.. NOTE::
Expand All @@ -83,14 +83,13 @@ def load(filename, globals, attach=False):
INPUT:
- ``filename`` -- a string; a .py, .sage, .pyx, etc., filename,
URL, or expression that evaluates to one
- ``filename`` -- a string denoting a filename or URL.
- ``globals`` -- a string:object dictionary; the context in which
to evaluate the ``filename`` and exec its contents
to execute the file contents.
- ``attach`` -- a boolean (default: False); whether to add the
file to the list of attached files
file to the list of attached files.
EXAMPLES:
Expand Down Expand Up @@ -128,17 +127,10 @@ def load(filename, globals, attach=False):
If the file isn't a Cython, Python, or a Sage file, a ValueError
is raised::
sage: sage.repl.load.load('a.foo',globals())
sage: sage.repl.load.load(tmp_filename(ext=".foo"), globals())
Traceback (most recent call last):
...
ValueError: argument (='a.foo') to load or attach must have extension py, pyx, sage, spyx, or m
A filename given as an expression get evaluated. This ensures
that ``load DATA+'foo.sage'`` works in the Notebook, say::
sage: t=tmp_filename(ext='.py'); open(t,'w').write("print 'hello world'")
sage: sage.repl.load.load(t, globals())
hello world
ValueError: unknown file extension '.foo' for load or attach (supported extensions: .py, .pyx, .sage, .spyx, .f, .f90, .m)
We load a file given at a remote URL::
Expand Down Expand Up @@ -189,69 +181,95 @@ def load(filename, globals, attach=False):
sage: load_attach_path() # not tested
['.', '/path/to/my/library', '/path/to/utils']
TESTS:
Make sure that load handles filenames with spaces in the name or path::
sage: t = tmp_filename(ext=' b.sage'); open(t,'w').write("print 2")
sage: sage.repl.load.load(t, globals())
2
Non-existing files with spaces give correct messages::
sage: sage.repl.load.load("this file should not exist", globals())
Traceback (most recent call last):
...
IOError: did not find file 'this file should not exist' to load or attach
Evaluating a filename is deprecated::
sage: sage.repl.load.load("tmp_filename(ext='.py')", globals())
doctest:...: DeprecationWarning: using unevaluated expressions as argument to load() is dangerous and deprecated
See http://trac.sagemath.org/17654 for details.
Test filenames separated by spaces (deprecated)::
sage: t = tmp_filename(ext='.py')
sage: with open(t, 'w') as f:
....: f.write("print 'hello'\n")
sage: sage.repl.load.load(t + " " + t, globals())
hello
hello
doctest:...: DeprecationWarning: using multiple filenames separated by spaces as load() argument is dangerous and deprecated
See http://trac.sagemath.org/17654 for details.
"""
if attach:
from sage.repl.attach import add_attached_file

try:
filename = eval(filename, globals)
except Exception:
# First check if the file exists. The filename may have spaces in
# its name, but more importantly modified_attached_files calls load
# with the absolute file path and that may contain spaces in the path
# As a side effect, this also allows file names with spaces in
# them, but currently I don't see a way to disallow this case.
if not os.path.exists(filename) and not os.path.isabs(filename):
# handle multiple input files separated by spaces, which was
filename = os.path.expanduser(filename)
if not os.path.exists(filename):
try:
# Try *evaluating* the filename
filename = eval(filename, globals).strip()
except Exception:
# Handle multiple input files separated by spaces, which was
# maybe a bad idea, but which we have to handle for backwards
# compatibility.
v = filename.split()
if len(v) > 1:
for file in v:
load(file, globals, attach=attach)
return

filename = filename.strip()
try:
for f in v:
load(f, globals, attach=attach)
except IOError:
# Splitting the filename didn't work, pretend it
# didn't happen :-)
pass
else:
# Only show deprecation message if the filename
# splitting worked.
from sage.misc.superseded import deprecation
deprecation(17654, 'using multiple filenames separated by spaces as load() argument is dangerous and deprecated')
return
else:
from sage.misc.superseded import deprecation
deprecation(17654, 'using unevaluated expressions as argument to load() is dangerous and deprecated')

if filename.lower().startswith(('http://', 'https://')):
if attach:
# But see http://en.wikipedia.org/wiki/HTTP_ETag for how
# we will do this.
# http://www.diveintopython.net/http_web_services/etags.html
raise NotImplementedError("you can't attach a URL")
from remote_file import get_remote_file
from sage.misc.remote_file import get_remote_file
filename = get_remote_file(filename, verbose=False)

if not is_loadable_filename(filename):
raise ValueError('argument (=%r) to load or attach must have extension py, pyx, sage, spyx, or m' % filename)

fpath = os.path.expanduser(filename)
if os.path.isabs(fpath):
if not os.path.exists(fpath):
raise IOError('did not find file %r to load or attach' % filename)
from sage.repl.attach import load_attach_path
for path in load_attach_path():
fpath = os.path.join(path, filename)
fpath = os.path.expanduser(fpath)
if os.path.isfile(fpath):
break
else:
from sage.repl.attach import load_attach_path
for path in load_attach_path():
fpath = os.path.join(path, filename)
fpath = os.path.expanduser(fpath)
if os.path.exists(fpath):
break
else:
raise IOError('did not find file %r in load / attach search path' \
% filename)
raise IOError('did not find file %r to load or attach' % filename)

if fpath.endswith('.py'):
ext = os.path.splitext(fpath)[1].lower()
if ext == '.py':
if attach:
add_attached_file(fpath)
with open(fpath) as f:
code = compile(f.read(), fpath, 'exec')
exec(code, globals)
elif fpath.endswith('.sage'):
elif ext == '.sage':
from sage.repl.attach import load_attach_mode
from sage.repl.preparse import preparse_file_named, preparse_file
load_debug_mode, attach_debug_mode = load_attach_mode()
Expand All @@ -270,17 +288,23 @@ def load(filename, globals, attach=False):
if attach:
add_attached_file(fpath)
exec(preparse_file(open(fpath).read()) + "\n", globals)
elif fpath.endswith('.spyx') or fpath.endswith('.pyx'):
elif ext == '.spyx' or ext == '.pyx':
if attach:
add_attached_file(fpath)
exec(load_cython(fpath), globals)
elif fpath.endswith('.m'):
elif ext == '.f' or ext == '.f90':
from sage.misc.inline_fortran import fortran
with open(fpath) as f:
fortran(f.read(), globals)
elif ext == '.m':
# Assume magma for now, though maybe .m is used by maple and
# mathematica too, and we should really analyze the file
# further.
s = globals['magma'].load(fpath)
i = s.find('\n'); s = s[i+1:]
print(s)
else:
raise ValueError('unknown file extension %r for load or attach (supported extensions: .py, .pyx, .sage, .spyx, .f, .f90, .m)' % ext)


def load_wrap(filename, attach=False):
Expand Down
Loading

0 comments on commit c23b064

Please sign in to comment.