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() # optional - sage_spkg
Traceback (most recent call last):
...
FeatureNotPresentError: random is not available.
Executable 'randomOochoz6x' not found on PATH.
...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 ontest_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 theExecutable
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:
The function 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.features.TrivialUniqueRepresentation
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.OUTPUT:
A string, a lazy string, or
None
. The default implementation always returns a lazy string.EXAMPLES:
sage: from sage.features import Executable sage: Executable(name="CSDP", spkg="csdp", executable="theta", url="https://github.com/dimpase/csdp").resolution() # optional - sage_spkg l'...To install CSDP...you can try to run...sage -i csdp...Further installation instructions might be available at https://github.com/dimpase/csdp.'
- exception sage.features.FeatureNotPresentError(feature, reason=None, resolution=None)¶
Bases:
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
andresolution
:sage: presence.reason '`TestPackageAvailability("NOT_A_PACKAGE")` evaluated to `fail` in GAP.' sage: bool(presence.resolution) False
If a feature is not present,
resolution
defaults tofeature.resolution()
if this is defined. If you do not want to use this default you need explicitly setresolution
to a string:sage: from sage.features import FeatureTestResult sage: package = GapPackage("NOT_A_PACKAGE", spkg="no_package") sage: str(FeatureTestResult(package, True).resolution) # optional - sage_spkg '...To install GAP package NOT_A_PACKAGE...you can try to run...sage -i no_package...' sage: str(FeatureTestResult(package, False).resolution) # optional - sage_spkg '...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.PackageSystem(name, spkg=None, url=None)¶
Bases:
sage.features.Feature
A feature describing a system package manager.
EXAMPLES:
sage: from sage.features import PackageSystem sage: PackageSystem('conda') Feature('conda')
- spkg_installation_hint(spkgs, prompt, feature)¶
Return a string that explains how to install
feature
.EXAMPLES:
sage: from sage.features import PackageSystem sage: homebrew = PackageSystem('homebrew') sage: homebrew.spkg_installation_hint('openblas') # optional - SAGE_ROOT 'To install openblas using the homebrew package manager, you can try to run:\n!brew install openblas'
- class sage.features.PipPackageSystem(name, spkg=None, url=None)¶
Bases:
sage.features.PackageSystem
The feature describing the Pip package manager.
EXAMPLES:
sage: from sage.features import PipPackageSystem sage: PipPackageSystem() Feature('pip')
- 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.SagePackageSystem(name, spkg=None, url=None)¶
Bases:
sage.features.PackageSystem
The feature describing the Sage package manager.
EXAMPLES:
sage: from sage.features import SagePackageSystem sage: SagePackageSystem() Feature('sage_spkg')
- 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() # optional - sage_spkg 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 import StaticFile sage: from sage.misc.temporary_file import tmp_dir sage: dir_with_file = tmp_dir() sage: file_path = os.path.join(dir_with_file, "file.txt") sage: open(file_path, 'a').close() # make sure the file exists sage: search_path = ( '/foo/bar', dir_with_file ) # file is somewhere in the search path sage: feature = StaticFile(name="file", filename="file.txt", search_path=search_path) sage: feature.absolute_path() == file_path True
A
FeatureNotPresentError
is raised if the file cannot 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() # optional - sage_spkg 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.
- class sage.features.TrivialClasscallMetaClass¶
Bases:
type
A trivial version of
ClasscallMetaclass
without Cython dependencies.
- class sage.features.TrivialUniqueRepresentation¶
Bases:
object
A trivial version of
UniqueRepresentation
without Cython dependencies.
- sage.features.package_systems()¶
Return a list of
PackageSystem
objects representing the available package systems.The list is ordered by decreasing preference.
EXAMPLES:
sage: from sage.features import package_systems sage: package_systems() # random [Feature('homebrew'), Feature('sage_spkg'), Feature('pip')]