Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Here's a rough sketch of a sage_import that works similarly to the Python import statement on .sage files, including searching sys.path for the file (which by default includes your current working directory), but this part is optional I suppose.

# sage_import.py
import imp
import inspect
import os
import sys

import sage.all


def sage_import(modname, fromlist=None, namespace=None):
    """
    Import a .sage module from the filename <modname>.sage

    Returns the resulting Python module.  If ``fromlist`` is given, returns
    just those members of the module into the global namespace where the
    function was called, or the given namespace.
    """

    filename = modname + '.sage'

    for path in sys.path:
        modpath = os.path.join(path, filename)
        if os.path.isfile(modpath):
            break
    else:
        raise ImportError('no file {} on sys.path'.format(filename))

    with open(modpath) as fobj:
        code = sage.all.preparse(fobj.read())
        mod = imp.new_module(modname)
        mod.__file__ = modpath
        # Fill with all the default Sage globals
        # We could just do a dict.update but we want to exclude dunder
        # and private attributes I guess
        for k, v in sage.all.__dict__.items():
            if not k.startswith('_'):
                mod.__dict__[k] = v

        exec code in mod.__dict__

    if namespace is None:
        namespace = inspect.currentframe().f_back.f_globals


    if fromlist is not None:
        # First check that each name in fromlist exists before adding
        # any of them to the given namespace.
        for name in fromlist:
            if name not in mod.__dict__:
                raise ImportError('cannot import name {!r} from {}'.format(
                     name, filename))

        for name in fromlist:
            namespace[name] = mod.__dict__[name]
    else:
        namespace[modname] = mod

Given a file in my current directory named a.sage containing:

# a.sage
a = 1 / 2

I can use this either in Sage or in a standard Python interpreter like

>>> from sage_import import sage_import
>>> sage_import('a', fromlist=['a'])
>>> a
1/2
>>> type(a)
<type 'sage.rings.rational.Rational'>

This version works for Python 2. It can be adapted to Python 3 with some small tweaks.

There is an open issue to build this functionality directly into Sage so that you can use the standard import statement to import .sage modules. This can be done but it requires a bit of care. I think a prototype was started at one point but it still hasn't been submitted.