Skip to content

Commit 839fdf1

Browse files
author
Release Manager
committed
sagemathgh-37024: Access database and other files through features, for simpler configuration This PR reworks the way sagemath access database files, and some other external files. The end result is that the runtime detection of databases as features matches exactly what will be used. Moreover, this leads to an easy implementation of search paths, meaning we don't need configuration as long as the files are in a few standard system locations. Notes on implementation: - First every user of these files access them via features, and never directly through `sage.env`, since the former is dynamic and the latter is static. - Then we add a variable `SAGE_DATA_PATH` which is a colon separated search path for databases. The default should work for sage-the-distro and most distros (think `/usr/share/sagemath:/usr/share` for a system install on `/usr`). - Now a database, say `cremona` is searched on `$p/cremona` for each `$p` in `SAGE_DATA_PATH`. - I also added `$DOT_SAGE/db` first in the search path, this makes it easy for a user to install a missing database (or update an old one). - Finally, as an example I did something similar to find `JmolData.jar` and `three.min.js`. The former is searched in `$SAGE_SHARE/sagemath/jmol` and `$SAGE_SHARE/jmol` and the latter also in `$SAGE_SHARE/jupyter/nbextensions/threejs-sage` which seems to make sense. Other files can be done later (e.g. mathjax). In principle anything that has a default value in `sage.env` or `sage_conf` should benefit from using a search path and removing the default value. With this PR, I can run stock sagemath without any `sage_conf.py`. ### 📝 Checklist - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have created tests covering the changes. ### ⌛ Dependencies URL: sagemath#37024 Reported by: Gonzalo Tornaría Reviewer(s): François Bissey, Gonzalo Tornaría, Matthias Köppe, Michael Orlitzky
2 parents b13f43f + 2d3b596 commit 839fdf1

File tree

19 files changed

+896
-125
lines changed

19 files changed

+896
-125
lines changed

src/sage/combinat/designs/MOLS_handbook_data.py

+571
Large diffs are not rendered by default.

src/sage/combinat/designs/latin_squares.py

+7-11
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
0| + +
7676
20|
7777
40|
78-
60| +
78+
60|
7979
80|
8080
100|
8181
120|
@@ -126,7 +126,6 @@
126126
from sage.rings.integer import Integer
127127
from sage.categories.sets_cat import EmptySetError
128128
from sage.misc.unknown import Unknown
129-
from sage.env import COMBINATORIAL_DESIGN_DATA_DIR
130129

131130

132131
def are_mutually_orthogonal_latin_squares(l, verbose=False):
@@ -500,13 +499,13 @@ def MOLS_table(start,stop=None,compare=False,width=None):
500499
0| + +
501500
20|
502501
40|
503-
60| +
502+
60|
504503
80|
505504
sage: MOLS_table(50, 100, compare=True)
506505
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
507506
________________________________________________________________________________
508507
40|
509-
60| +
508+
60|
510509
80|
511510
"""
512511
from .orthogonal_arrays import largest_available_k
@@ -520,11 +519,6 @@ def MOLS_table(start,stop=None,compare=False,width=None):
520519
if stop <= start:
521520
return
522521

523-
if compare:
524-
handbook_file = open("{}/MOLS_table.txt".format(COMBINATORIAL_DESIGN_DATA_DIR), 'r')
525-
hb = [int(_) for _ in handbook_file.readlines()[9].split(',')]
526-
handbook_file.close()
527-
528522
# choose an appropriate width (needs to be >= 3 because "+oo" should fit)
529523
if width is None:
530524
width = max(3, Integer(stop-1).ndigits(10))
@@ -537,9 +531,11 @@ def MOLS_table(start,stop=None,compare=False,width=None):
537531
print("\n{:>{width}}|".format(i, width=width), end="")
538532
k = largest_available_k(i)-2
539533
if compare:
540-
if i < 2 or hb[i] == k:
534+
from . import MOLS_handbook_data
535+
lower_bound = MOLS_handbook_data.lower_bound(i)
536+
if i < 2 or lower_bound == k:
541537
c = ""
542-
elif hb[i] < k:
538+
elif lower_bound < k:
543539
c = "+"
544540
else:
545541
c = "-"

src/sage/databases/jones.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@
7979

8080
from sage.features.databases import DatabaseJones
8181

82-
JONESDATA = os.path.join(SAGE_SHARE, 'jones') # should match the filename set in DatabaseJones
83-
8482

8583
def sortkey(K):
8684
"""
@@ -160,8 +158,10 @@ def _init(self, path):
160158
for Y in os.listdir(Z):
161159
if Y[-3:] == ".gp":
162160
self._load(Z, Y)
163-
os.makedirs(JONESDATA, exist_ok=True)
164-
save(self.root, JONESDATA + "/jones.sobj")
161+
162+
data_dir = os.path.dirname(DatabaseJones().absolute_filename())
163+
os.makedirs(data_dir, exist_ok=True)
164+
save(self.root, os.path.join(data_dir, "jones.sobj"))
165165

166166
def unramified_outside(self, S, d=None, var='a'):
167167
"""

src/sage/databases/sql_db.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ def construct_skeleton(database):
250250
skeleton = {}
251251
cur = database.__connection__.cursor()
252252
exe = cur.execute("SELECT name FROM sqlite_master WHERE TYPE='table'")
253-
from sage.env import GRAPHS_DATA_DIR
254253
for table in exe.fetchall():
255254
skeleton[table[0]] = {}
256255
exe1 = cur.execute("PRAGMA table_info(%s)" % table[0])
@@ -264,8 +263,7 @@ def construct_skeleton(database):
264263
exe2 = cur.execute("PRAGMA index_list(%s)" % table[0])
265264
for col in exe2.fetchall():
266265
if col[1].find('sqlite') == -1:
267-
if database.__dblocation__ == \
268-
os.path.join(GRAPHS_DATA_DIR,'graphs.db'):
266+
if os.path.basename(database.__dblocation__) == 'graphs.db':
269267
name = col[1]
270268
else:
271269
name = col[1][len(table[0])+3:]

src/sage/env.py

+20-11
Original file line numberDiff line numberDiff line change
@@ -195,18 +195,26 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st
195195
SAGE_ARCHFLAGS = var("SAGE_ARCHFLAGS", "unset")
196196
SAGE_PKG_CONFIG_PATH = var("SAGE_PKG_CONFIG_PATH")
197197

198+
# colon-separated search path for databases.
199+
SAGE_DATA_PATH = var("SAGE_DATA_PATH",
200+
os.pathsep.join(filter(None, [
201+
join(DOT_SAGE, "db"),
202+
join(SAGE_SHARE, "sagemath"),
203+
SAGE_SHARE,
204+
])))
205+
206+
# database directories, the default is to search in SAGE_DATA_PATH
207+
CREMONA_LARGE_DATA_DIR = var("CREMONA_LARGE_DATA_DIR")
208+
CREMONA_MINI_DATA_DIR = var("CREMONA_MINI_DATA_DIR")
209+
ELLCURVE_DATA_DIR = var("ELLCURVE_DATA_DIR")
210+
GRAPHS_DATA_DIR = var("GRAPHS_DATA_DIR")
211+
POLYTOPE_DATA_DIR = var("POLYTOPE_DATA_DIR")
212+
198213
# installation directories for various packages
199-
GRAPHS_DATA_DIR = var("GRAPHS_DATA_DIR", join(SAGE_SHARE, "graphs"))
200-
ELLCURVE_DATA_DIR = var("ELLCURVE_DATA_DIR", join(SAGE_SHARE, "ellcurves"))
201-
POLYTOPE_DATA_DIR = var("POLYTOPE_DATA_DIR", join(SAGE_SHARE, "reflexive_polytopes"))
202-
203-
COMBINATORIAL_DESIGN_DATA_DIR = var("COMBINATORIAL_DESIGN_DATA_DIR", join(SAGE_SHARE, "combinatorial_designs"))
204-
CREMONA_MINI_DATA_DIR = var("CREMONA_MINI_DATA_DIR", join(SAGE_SHARE, "cremona"))
205-
CREMONA_LARGE_DATA_DIR = var("CREMONA_LARGE_DATA_DIR", join(SAGE_SHARE, "cremona"))
206-
JMOL_DIR = var("JMOL_DIR", join(SAGE_SHARE, "jmol"))
214+
JMOL_DIR = var("JMOL_DIR")
207215
MATHJAX_DIR = var("MATHJAX_DIR", join(SAGE_SHARE, "mathjax"))
208216
MTXLIB = var("MTXLIB", join(SAGE_SHARE, "meataxe"))
209-
THREEJS_DIR = var("THREEJS_DIR", join(SAGE_SHARE, "threejs-sage"))
217+
THREEJS_DIR = var("THREEJS_DIR")
210218
PPLPY_DOCS = var("PPLPY_DOCS", join(SAGE_SHARE, "doc", "pplpy"))
211219
MAXIMA = var("MAXIMA", "maxima")
212220
MAXIMA_FAS = var("MAXIMA_FAS")
@@ -313,6 +321,7 @@ def sage_include_directories(use_sources=False):
313321

314322
return dirs
315323

324+
316325
def get_cblas_pc_module_name() -> str:
317326
"""
318327
Return the name of the BLAS libraries to be used.
@@ -420,7 +429,7 @@ def cython_aliases(required_modules=None,
420429
aliases["ECL_INCDIR"] = list(map(lambda s: s[2:], filter(lambda s: s.startswith('-I'), ecl_cflags)))
421430
aliases["ECL_LIBDIR"] = list(map(lambda s: s[2:], filter(lambda s: s.startswith('-L'), ecl_libs)))
422431
aliases["ECL_LIBRARIES"] = list(map(lambda s: s[2:], filter(lambda s: s.startswith('-l'), ecl_libs)))
423-
aliases["ECL_LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l','-L')), ecl_libs))
432+
aliases["ECL_LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l', '-L')), ecl_libs))
424433
continue
425434
else:
426435
try:
@@ -439,7 +448,7 @@ def cython_aliases(required_modules=None,
439448
# include search order matters.
440449
aliases[var + "INCDIR"] = pc['include_dirs']
441450
aliases[var + "LIBDIR"] = pc['library_dirs']
442-
aliases[var + "LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l','-L')), libs.split()))
451+
aliases[var + "LIBEXTRA"] = list(filter(lambda s: not s.startswith(('-l', '-L')), libs.split()))
443452
aliases[var + "LIBRARIES"] = pc['libraries']
444453

445454
# uname-specific flags

src/sage/features/__init__.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ def unhide(self):
416416
"""
417417
self._hidden = False
418418

419+
419420
class FeatureNotPresentError(RuntimeError):
420421
r"""
421422
A missing feature error.
@@ -791,26 +792,37 @@ class StaticFile(FileFeature):
791792
EXAMPLES::
792793
793794
sage: from sage.features import StaticFile
794-
sage: StaticFile(name="no_such_file", filename="KaT1aihu", search_path=("/",), spkg="some_spkg", url="http://rand.om").require() # optional - sage_spkg
795+
sage: StaticFile(name="no_such_file", filename="KaT1aihu", # optional - sage_spkg
796+
....: search_path="/", spkg="some_spkg",
797+
....: url="http://rand.om").require()
795798
Traceback (most recent call last):
796799
...
797800
FeatureNotPresentError: no_such_file is not available.
798801
'KaT1aihu' not found in any of ['/']...
799802
To install no_such_file...you can try to run...sage -i some_spkg...
800803
Further installation instructions might be available at http://rand.om.
801804
"""
802-
def __init__(self, name, filename, search_path=None, **kwds):
805+
def __init__(self, name, filename, *, search_path=None, **kwds):
803806
r"""
804807
TESTS::
805808
806809
sage: from sage.features import StaticFile
807-
sage: StaticFile(name="null", filename="null", search_path=("/dev",))
810+
sage: StaticFile(name="null", filename="null", search_path="/dev")
808811
Feature('null')
812+
sage: sh = StaticFile(name="shell", filename="sh",
813+
....: search_path=("/dev", "/bin", "/usr"))
814+
sage: sh
815+
Feature('shell')
816+
sage: sh.absolute_filename()
817+
'/bin/sh'
818+
809819
"""
810820
Feature.__init__(self, name, **kwds)
811821
self.filename = filename
812822
if search_path is None:
813823
self.search_path = [SAGE_SHARE]
824+
elif isinstance(search_path, str):
825+
self.search_path = [search_path]
814826
else:
815827
self.search_path = list(search_path)
816828

0 commit comments

Comments
 (0)