Testing for features of the environment at runtime

A computation can require a certain package to be installed in the runtime environment. Abstractly such a package describes a :class`Feature` which can be tested for at runtime. It can be of various kinds, most prominently an Executable in the PATH or an additional package for some installed system such as a GapPackage.

AUTHORS:

  • Julian Rüth (2016-04-07): Initial version
  • Jeroen Demeyer (2018-02-12): Refactoring and clean up

EXAMPLES:

Some generic features are available for common cases. For example, to test for the existence of a binary, one can use an Executable feature:

sage: from sage.features import Executable
sage: Executable(name="sh", executable="sh").is_present()
FeatureTestResult('sh', True)

Here we test whether the grape GAP package is available:

sage: from sage.features.gap import GapPackage
sage: GapPackage("grape", spkg="gap_packages").is_present()  # optional: gap_packages
FeatureTestResult('GAP package grape', True)

Note that a FeatureTestResult acts like a bool in most contexts:

sage: if Executable(name="sh", executable="sh").is_present(): "present."
'present.'

When one wants to raise an error if the feature is not available, one can use the require method:

sage: Executable(name="sh", executable="sh").require()

sage: Executable(name="random", executable="randomOochoz6x", spkg="random", url="http://rand.om").require()
Traceback (most recent call last):
...
FeatureNotPresentError: random is not available.
Executable 'randomOochoz6x' not found on PATH.
To install random you can try to run 'sage -i random'.
Further installation instructions might be available at http://rand.om.

As can be seen above, features try to produce helpful error messages.

class sage.features.CythonFeature(name, test_code, **kwds)

Bases: sage.features.Feature

A Feature which describes the ability to compile and import a particular piece of Cython code.

To test the presence of name, the cython compiler is run on test_code and the resulting module is imported.

EXAMPLES:

sage: from sage.features import CythonFeature
sage: fabs_test_code = '''
....: cdef extern from "<math.h>":
....:     double fabs(double x)
....:
....: assert fabs(-1) == 1
....: '''
sage: fabs = CythonFeature("fabs", test_code=fabs_test_code, spkg="gcc", url="https://gnu.org")
sage: fabs.is_present()
FeatureTestResult('fabs', True)

Test various failures:

sage: broken_code = '''this is not a valid Cython program!'''
sage: broken = CythonFeature("broken", test_code=broken_code)
sage: broken.is_present()
FeatureTestResult('broken', False)
sage: broken_code = '''cdef extern from "no_such_header_file": pass'''
sage: broken = CythonFeature("broken", test_code=broken_code)
sage: broken.is_present()
FeatureTestResult('broken', False)
sage: broken_code = '''import no_such_python_module'''
sage: broken = CythonFeature("broken", test_code=broken_code)
sage: broken.is_present()
FeatureTestResult('broken', False)
sage: broken_code = '''raise AssertionError("sorry!")'''
sage: broken = CythonFeature("broken", test_code=broken_code)
sage: broken.is_present()
FeatureTestResult('broken', False)
class sage.features.Executable(name, executable, **kwds)

Bases: sage.features.Feature

A feature describing an executable in the PATH.

Note

Overwrite is_functional() if you also want to check whether the executable shows proper behaviour.

Calls to is_present() are cached. You might want to cache the Executable object to prevent unnecessary calls to the executable.

EXAMPLES:

sage: from sage.features import Executable
sage: Executable(name="sh", executable="sh").is_present()
FeatureTestResult('sh', True)
is_functional()

Return whether an executable in the path is functional.

EXAMPLES:

Returns True unless explicitly overwritten:

sage: from sage.features import Executable
sage: Executable(name="sh", executable="sh").is_functional()
FeatureTestResult('sh', True)
class sage.features.Feature(name, spkg=None, url=None)

Bases: sage.structure.unique_representation.UniqueRepresentation

A feature of the runtime environment

Overwrite _is_present() to add feature checks.

EXAMPLES:

sage: from sage.features.gap import GapPackage
sage: GapPackage("grape", spkg="gap_packages")  # indirect doctest
Feature('GAP package grape')

For efficiency, features are unique:

sage: GapPackage("grape") is GapPackage("grape")
True
is_present()

Return whether the feature is present.

OUTPUT:

A FeatureTestResult which can be used as a boolean and contains additional information about the feature test.

EXAMPLES:

sage: from sage.features.gap import GapPackage
sage: GapPackage("grape", spkg="gap_packages").is_present()  # optional: gap_packages
FeatureTestResult('GAP package grape', True)
sage: GapPackage("NOT_A_PACKAGE", spkg="gap_packages").is_present()
FeatureTestResult('GAP package NOT_A_PACKAGE', False)

The result is cached:

sage: from sage.features import Feature
sage: class TestFeature(Feature):
....:     def _is_present(self):
....:         print("checking presence")
....:         return True
sage: TestFeature("test").is_present()
checking presence
FeatureTestResult('test', True)
sage: TestFeature("test").is_present()
FeatureTestResult('test', True)
sage: TestFeature("other").is_present()
checking presence
FeatureTestResult('other', True)
sage: TestFeature("other").is_present()
FeatureTestResult('other', True)
require()

Raise a FeatureNotPresentError if the feature is not present.

EXAMPLES:

sage: from sage.features.gap import GapPackage
sage: GapPackage("ve1EeThu").require()
Traceback (most recent call last):
...
FeatureNotPresentError: GAP package ve1EeThu is not available.
`TestPackageAvailability("ve1EeThu")` evaluated to `fail` in GAP.
resolution()

Return a suggestion on how to make is_present() pass if it did not pass.

EXAMPLES:

sage: from sage.features import Executable
sage: Executable(name="CSDP", spkg="csdp", executable="theta", url="http://github.org/dimpase/csdp").resolution()
"To install CSDP you can try to run 'sage -i csdp'.\nFurther installation instructions might be available at http://github.org/dimpase/csdp."
exception sage.features.FeatureNotPresentError(feature, reason=None, resolution=None)

Bases: exceptions.RuntimeError

A missing feature error.

EXAMPLES:

sage: from sage.features import Feature, FeatureTestResult
sage: class Missing(Feature):
....:     def _is_present(self):
....:         return False

sage: Missing(name="missing").require()
Traceback (most recent call last):
...
FeatureNotPresentError: missing is not available.
class sage.features.FeatureTestResult(feature, is_present, reason=None, resolution=None)

Bases: object

The result of a Feature.is_present() call.

Behaves like a boolean with some extra data which may explain why a feature is not present and how this may be resolved.

EXAMPLES:

sage: from sage.features.gap import GapPackage
sage: presence = GapPackage("NOT_A_PACKAGE").is_present(); presence  # indirect doctest
FeatureTestResult('GAP package NOT_A_PACKAGE', False)
sage: bool(presence)
False

Explanatory messages might be available as reason and resolution:

sage: presence.reason
'`TestPackageAvailability("NOT_A_PACKAGE")` evaluated to `fail` in GAP.'
sage: print(presence.resolution)
None

If a feature is not present, resolution defaults to feature.resolution() if this is defined. If you do not want to use this default you need explicitly set resolution to a string:

sage: from sage.features import FeatureTestResult
sage: package = GapPackage("NOT_A_PACKAGE", spkg="no_package")
sage: FeatureTestResult(package, True).resolution
"To install GAP package NOT_A_PACKAGE you can try to run 'sage -i no_package'."
sage: FeatureTestResult(package, False).resolution
"To install GAP package NOT_A_PACKAGE you can try to run 'sage -i no_package'."
sage: FeatureTestResult(package, False, resolution="rtm").resolution
'rtm'
class sage.features.PythonModule(name, **kwds)

Bases: sage.features.Feature

A Feature which describes whether a python module can be imported.

EXAMPLES:

Not all builds of python include the ssl module, so you could check whether it is available:

sage: from sage.features import PythonModule
sage: PythonModule("ssl").require()  # not tested - output depends on the python build
class sage.features.StaticFile(name, filename, search_path=None, **kwds)

Bases: sage.features.Feature

A Feature which describes the presence of a certain file such as a database.

EXAMPLES:

sage: from sage.features import StaticFile
sage: StaticFile(name="no_such_file", filename="KaT1aihu", search_path=("/",), spkg="some_spkg", url="http://rand.om").require()
Traceback (most recent call last):
...
FeatureNotPresentError: no_such_file is not available.
'KaT1aihu' not found in any of ['/']
To install no_such_file you can try to run 'sage -i some_spkg'.
Further installation instructions might be available at http://rand.om.
absolute_path()

The absolute path of the file.

EXAMPLES:

sage: from sage.features.databases import DatabaseCremona
sage: DatabaseCremona().absolute_path()  # optional: database_cremona_ellcurve
'.../local/share/cremona/cremona.db'

A FeatureNotPresentError is raised if the file can not be found:

sage: from sage.features import StaticFile
sage: StaticFile(name="no_such_file", filename="KaT1aihu", search_path=(), spkg="some_spkg", url="http://rand.om").absolute_path()
Traceback (most recent call last):
...
FeatureNotPresentError: no_such_file is not available.
'KaT1aihu' not found in any of []
To install no_such_file you can try to run 'sage -i some_spkg'.
Further installation instructions might be available at http://rand.om.