Skip to content

Commit

Permalink
Trac #32614: Features and optional tags for sage modules provided by …
Browse files Browse the repository at this point in the history
…separate distributions

... so that we can start writing `# optional - sage.symbolic` and
similar.

We use it in #32432 ('''sagemath-polyhedra''') to skip doctests that
depend on `sage.graphs`, `sage.combinat`, `sage.rings.number_field` etc.

URL: https://trac.sagemath.org/32614
Reported by: mkoeppe
Ticket author(s): Matthias Koeppe
Reviewer(s): John Palmieri, Travis Scrimshaw
  • Loading branch information
Release Manager committed Oct 13, 2021
2 parents d694413 + 4558791 commit b743b48
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 9 deletions.
5 changes: 3 additions & 2 deletions src/doc/en/developer/coding_basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1121,9 +1121,10 @@ framework. Here is a comprehensive list:
.. NOTE::

- Any words after ``# optional`` are interpreted as a list of
package names, separated by spaces.
package (spkg) names or other feature tags, separated by spaces.

- Any punctuation (periods, commas, hyphens, semicolons, ...) after the
- Any punctuation other than underscores (``_``) and periods (``.``),
that is, commas, hyphens, semicolons, ..., after the
first word ends the list of packages. Hyphens or colons between the
word ``optional`` and the first package name are allowed. Therefore,
you should not write ``optional: needs package CHomP`` but simply
Expand Down
5 changes: 4 additions & 1 deletion src/sage/doctest/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from .parsing import parse_optional_tags

nodoctest_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*nodoctest')
optionaltag_regex = re.compile(r'^\w+$')
optionaltag_regex = re.compile(r'^(\w|[.])+$')
optionalfiledirective_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*sage\.doctest: (.*)')

# Optional tags which are always automatically added
Expand Down Expand Up @@ -407,6 +407,9 @@ def __init__(self, options, args):
from sage.features import package_systems
options.optional.update(system.name for system in package_systems())

from sage.features.sagemath import sage_features
options.optional.update(feature.name for feature in sage_features())

# Check that all tags are valid
for o in options.optional:
if not optionaltag_regex.search(o):
Expand Down
4 changes: 3 additions & 1 deletion src/sage/doctest/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .external import available_software

float_regex = re.compile(r'\s*([+-]?\s*((\d*\.?\d+)|(\d+\.?))([eE][+-]?\d+)?)')
optional_regex = re.compile(r'(arb216|arb218|py2|py3|long time|not implemented|not tested|known bug)|([^ a-z]\s*optional\s*[:-]*((\s|\w)*))')
optional_regex = re.compile(r'(arb216|arb218|py2|py3|long time|not implemented|not tested|known bug)|([^ a-z]\s*optional\s*[:-]*((\s|\w|[.])*))')
# Version 4.65 of glpk prints the warning "Long-step dual simplex will
# be used" frequently. When Sage uses a system installation of glpk
# which has not been patched, we need to ignore that message.
Expand Down Expand Up @@ -324,6 +324,8 @@ def parse_optional_tags(string):
{''}
sage: sorted(list(parse_optional_tags("sage: #optional -- foo bar, baz")))
['bar', 'foo']
sage: parse_optional_tags("sage: #optional -- foo.bar, baz")
{'foo.bar'}
sage: sorted(list(parse_optional_tags(" sage: factor(10^(10^10) + 1) # LoNg TiME, NoT TeSTED; OptioNAL -- P4cka9e")))
['long time', 'not tested', 'p4cka9e']
sage: parse_optional_tags(" sage: raise RuntimeError # known bug")
Expand Down
14 changes: 9 additions & 5 deletions src/sage/features/bliss.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Checks for bliss
"""
from . import CythonFeature, PythonModule
from .join_feature import JoinFeature


TEST_CODE = """
Expand Down Expand Up @@ -45,10 +46,10 @@ def __init__(self):
url="http://www.tcs.hut.fi/Software/bliss/")


class Bliss(PythonModule):
class Bliss(JoinFeature):
r"""
A :class:`Feature` which describes whether the :mod:`sage.graphs.bliss`
module has been enabled for this build of Sage and is functional.
module is available in this installation of Sage.
EXAMPLES::
Expand All @@ -61,7 +62,10 @@ def __init__(self):
sage: from sage.features.bliss import Bliss
sage: Bliss()
Feature('sage.graphs.bliss')
Feature('bliss')
"""
PythonModule.__init__(self, "sage.graphs.bliss", spkg="bliss",
url="http://www.tcs.hut.fi/Software/bliss/")
# Currently part of sagemath_standard, conditionally built.
# Will be changed to spkg='sagemath_bliss' later
JoinFeature.__init__(self, "bliss",
[PythonModule("sage.graphs.bliss", spkg="bliss",
url="http://www.tcs.hut.fi/Software/bliss/")])
11 changes: 11 additions & 0 deletions src/sage/features/mcqd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from . import PythonModule
from .join_feature import JoinFeature


class Mcqd(JoinFeature):

def __init__(self):
# Currently part of sagemath_standard, conditionally built.
# Will be changed to spkg='sagemath_mcqd' later
JoinFeature.__init__(self, 'mcqd',
[PythonModule('sage.graphs.mcqd', spkg='mcqd')])
11 changes: 11 additions & 0 deletions src/sage/features/meataxe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from . import PythonModule
from .join_feature import JoinFeature


class Meataxe(JoinFeature):

def __init__(self):
# Currently part of sagemath_standard, conditionally built.
# Will be changed to spkg='sagemath_meataxe' later
JoinFeature.__init__(self, 'meataxe',
[PythonModule('sage.matrix.matrix_gfpn_dense', spkg='meataxe')])
84 changes: 84 additions & 0 deletions src/sage/features/sagemath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
r"""
Check for SageMath Python modules
"""
from . import PythonModule
from .join_feature import JoinFeature


class sage__combinat(JoinFeature):

def __init__(self):
# sage.combinat will be a namespace package.
# Testing whether sage.combinat itself can be imported is meaningless.
# Hence, we test a Python module within the package.
JoinFeature.__init__(self, 'sage.combinat',
[PythonModule('sage.combinat.combinations')])


class sage__graphs(JoinFeature):

def __init__(self):
JoinFeature.__init__(self, 'sage.graphs',
[PythonModule('sage.graphs.graph')])


class sage__plot(JoinFeature):

def __init__(self):
JoinFeature.__init__(self, 'sage.plot',
[PythonModule('sage.plot.plot')])


class sage__rings__number_field(JoinFeature):

def __init__(self):
JoinFeature.__init__(self, 'sage.rings.number_field',
[PythonModule('sage.rings.number_field.number_field_element')])


class sage__rings__real_double(PythonModule):

def __init__(self):
PythonModule.__init__(self, 'sage.rings.real_double')


class sage__symbolic(JoinFeature):

def __init__(self):
JoinFeature.__init__(self, 'sage.symbolic',
[PythonModule('sage.symbolic.expression')],
spkg="sagemath_symbolics")


def sage_features():
"""
Return features corresponding to parts of the Sage library.
These tags are named after Python packages/modules (e.g., :mod:`~sage.symbolic`),
not distribution packages (``sagemath-symbolics``).
This design is motivated by a separation of concerns: The author of a module that depends
on some functionality provided by a Python module usually already knows the
name of the Python module, so we do not want to force the author to also
know about the distribution package that provides the Python module.
Instead, we associate distribution packages to Python modules in
:mod:`sage.features.sagemath` via the ``spkg`` parameter of :class:`Feature`.
EXAMPLES::
sage: from sage.features.sagemath import sage_features
sage: list(sage_features()) # random
[Feature('sage.graphs'),
Feature('sage.plot'),
Feature('sage.rings.number_field'),
Feature('sage.rings.real_double')]
"""
for feature in [sage__combinat(),
sage__graphs(),
sage__plot(),
sage__rings__number_field(),
sage__rings__real_double(),
sage__symbolic()]:
if feature.is_present():
yield feature
11 changes: 11 additions & 0 deletions src/sage/features/tdlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from . import PythonModule
from .join_feature import JoinFeature


class Tdlib(JoinFeature):

def __init__(self):
# Currently part of sagemath_standard, conditionally built.
# Will be changed to spkg='sagemath_tdlib' later
JoinFeature.__init__(self, 'tdlib',
[PythonModule('sage.graphs.graph_decompositions.tdlib', spkg='tdlib')])

0 comments on commit b743b48

Please sign in to comment.