passagemath-environment 10.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_environment-10.4.1.data/scripts/sage +1140 -0
- passagemath_environment-10.4.1.data/scripts/sage-env +667 -0
- passagemath_environment-10.4.1.data/scripts/sage-num-threads.py +105 -0
- passagemath_environment-10.4.1.data/scripts/sage-python +2 -0
- passagemath_environment-10.4.1.data/scripts/sage-venv-config +42 -0
- passagemath_environment-10.4.1.data/scripts/sage-version.sh +9 -0
- passagemath_environment-10.4.1.dist-info/METADATA +76 -0
- passagemath_environment-10.4.1.dist-info/RECORD +70 -0
- passagemath_environment-10.4.1.dist-info/WHEEL +5 -0
- passagemath_environment-10.4.1.dist-info/top_level.txt +1 -0
- sage/all__sagemath_environment.py +4 -0
- sage/env.py +496 -0
- sage/features/__init__.py +981 -0
- sage/features/all.py +126 -0
- sage/features/bliss.py +85 -0
- sage/features/cddlib.py +38 -0
- sage/features/coxeter3.py +45 -0
- sage/features/csdp.py +83 -0
- sage/features/cython.py +38 -0
- sage/features/databases.py +302 -0
- sage/features/dvipng.py +40 -0
- sage/features/ecm.py +42 -0
- sage/features/ffmpeg.py +119 -0
- sage/features/four_ti_2.py +55 -0
- sage/features/fricas.py +66 -0
- sage/features/gap.py +86 -0
- sage/features/gfan.py +38 -0
- sage/features/giac.py +30 -0
- sage/features/graph_generators.py +171 -0
- sage/features/graphviz.py +117 -0
- sage/features/igraph.py +44 -0
- sage/features/imagemagick.py +138 -0
- sage/features/interfaces.py +256 -0
- sage/features/internet.py +65 -0
- sage/features/jmol.py +44 -0
- sage/features/join_feature.py +146 -0
- sage/features/kenzo.py +77 -0
- sage/features/latex.py +300 -0
- sage/features/latte.py +85 -0
- sage/features/lrs.py +164 -0
- sage/features/mcqd.py +45 -0
- sage/features/meataxe.py +46 -0
- sage/features/mip_backends.py +114 -0
- sage/features/msolve.py +68 -0
- sage/features/nauty.py +70 -0
- sage/features/normaliz.py +43 -0
- sage/features/palp.py +65 -0
- sage/features/pandoc.py +42 -0
- sage/features/pdf2svg.py +41 -0
- sage/features/phitigra.py +42 -0
- sage/features/pkg_systems.py +195 -0
- sage/features/polymake.py +43 -0
- sage/features/poppler.py +58 -0
- sage/features/rubiks.py +180 -0
- sage/features/sagemath.py +1205 -0
- sage/features/sat.py +103 -0
- sage/features/singular.py +48 -0
- sage/features/sirocco.py +45 -0
- sage/features/sphinx.py +71 -0
- sage/features/standard.py +38 -0
- sage/features/symengine_py.py +44 -0
- sage/features/tdlib.py +38 -0
- sage/features/threejs.py +75 -0
- sage/features/topcom.py +67 -0
- sage/misc/all__sagemath_environment.py +2 -0
- sage/misc/package.py +570 -0
- sage/misc/package_dir.py +621 -0
- sage/misc/temporary_file.py +546 -0
- sage/misc/viewer.py +369 -0
- sage/version.py +5 -0
sage/misc/package.py
ADDED
@@ -0,0 +1,570 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-environment
|
2
|
+
r"""
|
3
|
+
Listing Sage packages
|
4
|
+
|
5
|
+
This module can be used to see which Sage packages are installed
|
6
|
+
and which packages are available for installation.
|
7
|
+
|
8
|
+
For more information about creating Sage packages, see the "Packaging
|
9
|
+
Third-Party Code" section of the Sage Developer's Guide.
|
10
|
+
|
11
|
+
Actually installing the packages should be done via the command
|
12
|
+
line, using the following commands:
|
13
|
+
|
14
|
+
- ``sage -i PACKAGE_NAME`` -- install the given package
|
15
|
+
|
16
|
+
- ``sage -f PACKAGE_NAME`` -- re-install the given package, even if it
|
17
|
+
was already installed
|
18
|
+
|
19
|
+
To list the packages available, either use in a terminal one of ``sage
|
20
|
+
-standard``, ``sage -optional`` or ``sage -experimental``. Or the following
|
21
|
+
command inside Sage::
|
22
|
+
|
23
|
+
sage: from sage.misc.package import list_packages
|
24
|
+
sage: pkgs = list_packages(local=True) # optional - sage_spkg
|
25
|
+
sage: sorted(pkgs.keys()) # optional - sage_spkg, random
|
26
|
+
['4ti2',
|
27
|
+
'alabaster',
|
28
|
+
...
|
29
|
+
'zlib']
|
30
|
+
|
31
|
+
Functions
|
32
|
+
---------
|
33
|
+
"""
|
34
|
+
|
35
|
+
# ****************************************************************************
|
36
|
+
# This program is free software: you can redistribute it and/or modify
|
37
|
+
# it under the terms of the GNU General Public License as published by
|
38
|
+
# the Free Software Foundation, either version 2 of the License, or
|
39
|
+
# (at your option) any later version.
|
40
|
+
# https://www.gnu.org/licenses/
|
41
|
+
# ****************************************************************************
|
42
|
+
from typing import NamedTuple, Optional, Union
|
43
|
+
|
44
|
+
import sage.env
|
45
|
+
|
46
|
+
import json
|
47
|
+
import os
|
48
|
+
import subprocess
|
49
|
+
import sys
|
50
|
+
from pathlib import Path
|
51
|
+
from urllib.request import urlopen
|
52
|
+
from urllib.error import URLError
|
53
|
+
from ssl import create_default_context as default_context
|
54
|
+
|
55
|
+
DEFAULT_PYPI = 'https://pypi.org/pypi'
|
56
|
+
|
57
|
+
|
58
|
+
def pkgname_split(name):
|
59
|
+
r"""
|
60
|
+
Split a pkgname into a list of strings, 'name, version'.
|
61
|
+
|
62
|
+
For some packages, the version string might be empty.
|
63
|
+
|
64
|
+
EXAMPLES::
|
65
|
+
|
66
|
+
sage: from sage.misc.package import pkgname_split
|
67
|
+
sage: pkgname_split('hello_world-1.2')
|
68
|
+
['hello_world', '1.2']
|
69
|
+
"""
|
70
|
+
return (name.split('-', 1) + [''])[:2]
|
71
|
+
|
72
|
+
|
73
|
+
def pip_remote_version(pkg, pypi_url=DEFAULT_PYPI, ignore_URLError=False):
|
74
|
+
r"""
|
75
|
+
Return the version of this pip package available on PyPI.
|
76
|
+
|
77
|
+
INPUT:
|
78
|
+
|
79
|
+
- ``pkg`` -- the package
|
80
|
+
|
81
|
+
- ``pypi_url`` -- string (default: standard PyPI url) an optional Python
|
82
|
+
package repository to use
|
83
|
+
|
84
|
+
- ``ignore_URLError`` -- boolean (default: ``False``); if set to ``True`` then no
|
85
|
+
error is raised if the connection fails and the function returns ``None``
|
86
|
+
|
87
|
+
EXAMPLES:
|
88
|
+
|
89
|
+
The following test does fail if there is no TLS support (see e.g.
|
90
|
+
:issue:`19213`)::
|
91
|
+
|
92
|
+
sage: from sage.misc.package import pip_remote_version
|
93
|
+
sage: pip_remote_version('beautifulsoup4') # optional - internet # not tested
|
94
|
+
'...'
|
95
|
+
|
96
|
+
These tests are reliable since the tested package does not exist::
|
97
|
+
|
98
|
+
sage: nap = 'hey_this_is_NOT_a_python_package'
|
99
|
+
sage: pypi = 'http://this.is.not.pypi.com/'
|
100
|
+
sage: pip_remote_version(nap, pypi_url=pypi, ignore_URLError=True) # optional - internet
|
101
|
+
doctest:...: UserWarning: failed to fetch the version of
|
102
|
+
pkg='hey_this_is_NOT_a_python_package' at
|
103
|
+
http://this.is.not.pypi.com/.../json
|
104
|
+
sage: pip_remote_version(nap, pypi_url=pypi, ignore_URLError=False) # optional - internet
|
105
|
+
Traceback (most recent call last):
|
106
|
+
...
|
107
|
+
HTTPError: HTTP Error 404: Not Found
|
108
|
+
"""
|
109
|
+
url = '{pypi_url}/{pkg}/json'.format(pypi_url=pypi_url, pkg=pkg)
|
110
|
+
|
111
|
+
try:
|
112
|
+
f = urlopen(url, context=default_context())
|
113
|
+
text = f.read()
|
114
|
+
f.close()
|
115
|
+
except URLError:
|
116
|
+
if ignore_URLError:
|
117
|
+
import warnings
|
118
|
+
warnings.warn("failed to fetch the version of pkg={!r} at {}".format(pkg, url))
|
119
|
+
return
|
120
|
+
else:
|
121
|
+
raise
|
122
|
+
|
123
|
+
info = json.loads(text)
|
124
|
+
stable_releases = [v for v in info['releases'] if 'a' not in v and 'b' not in v]
|
125
|
+
return max(stable_releases)
|
126
|
+
|
127
|
+
|
128
|
+
def spkg_type(name):
|
129
|
+
r"""
|
130
|
+
Return the type of the Sage package with the given name.
|
131
|
+
|
132
|
+
INPUT:
|
133
|
+
|
134
|
+
- ``name`` -- string giving the subdirectory name of the package under
|
135
|
+
``SAGE_PKGS``
|
136
|
+
|
137
|
+
EXAMPLES::
|
138
|
+
|
139
|
+
sage: from sage.misc.package import spkg_type
|
140
|
+
sage: spkg_type('pip') # optional - sage_spkg
|
141
|
+
'standard'
|
142
|
+
|
143
|
+
OUTPUT:
|
144
|
+
|
145
|
+
The type as a string in ``('base', 'standard', 'optional', 'experimental')``.
|
146
|
+
If no ``SPKG`` exists with the given name (or the directory ``SAGE_PKGS`` is
|
147
|
+
not available), ``None`` is returned.
|
148
|
+
"""
|
149
|
+
spkg_type = None
|
150
|
+
from sage.env import SAGE_PKGS
|
151
|
+
if not SAGE_PKGS:
|
152
|
+
return None
|
153
|
+
try:
|
154
|
+
f = open(os.path.join(SAGE_PKGS, name, "type"))
|
155
|
+
except OSError:
|
156
|
+
# Probably an empty directory => ignore
|
157
|
+
return None
|
158
|
+
|
159
|
+
with f:
|
160
|
+
spkg_type = f.read().strip()
|
161
|
+
return spkg_type
|
162
|
+
|
163
|
+
|
164
|
+
def pip_installed_packages(normalization=None):
|
165
|
+
r"""
|
166
|
+
Return a dictionary `name->version` of installed pip packages.
|
167
|
+
|
168
|
+
This command returns *all* pip-installed packages. Not only Sage packages.
|
169
|
+
|
170
|
+
INPUT:
|
171
|
+
|
172
|
+
- ``normalization`` -- (default: ``None``) according to which rule to
|
173
|
+
normalize the package name, either ``None`` (as is) or ``'spkg'`` (format
|
174
|
+
as in the Sage distribution in ``build/pkgs/``), i.e., lowercased and
|
175
|
+
dots and dashes replaced by underscores.
|
176
|
+
|
177
|
+
EXAMPLES::
|
178
|
+
|
179
|
+
sage: # optional - sage_spkg
|
180
|
+
sage: from sage.misc.package import pip_installed_packages
|
181
|
+
sage: d = pip_installed_packages()
|
182
|
+
sage: 'scipy' in d or 'SciPy' in d # needs scipy
|
183
|
+
True
|
184
|
+
sage: 'beautifulsoup4' in d # needs beautifulsoup4
|
185
|
+
True
|
186
|
+
sage: 'prompt-toolkit' in d or 'prompt_toolkit' in d # whether - or _ appears in the name depends on the setuptools version used for building the package
|
187
|
+
True
|
188
|
+
sage: d = pip_installed_packages(normalization='spkg')
|
189
|
+
sage: d['prompt_toolkit']
|
190
|
+
'...'
|
191
|
+
sage: d['scipy'] # needs scipy
|
192
|
+
'...'
|
193
|
+
"""
|
194
|
+
with open(os.devnull, 'w') as devnull:
|
195
|
+
proc = subprocess.Popen(
|
196
|
+
[sys.executable, "-m", "pip", "list", "--no-index", "--format", "json"],
|
197
|
+
stdout=subprocess.PIPE,
|
198
|
+
stderr=devnull,
|
199
|
+
)
|
200
|
+
stdout = proc.communicate()[0].decode()
|
201
|
+
|
202
|
+
def normalize(name: str) -> str:
|
203
|
+
if normalization is None:
|
204
|
+
return name
|
205
|
+
elif normalization == 'spkg':
|
206
|
+
return name.lower().replace('-', '_').replace('.', '_')
|
207
|
+
else:
|
208
|
+
raise NotImplementedError(f'normalization {normalization} is not implemented')
|
209
|
+
try:
|
210
|
+
return {normalize(package['name']): package['version']
|
211
|
+
for package in json.loads(stdout)}
|
212
|
+
except json.decoder.JSONDecodeError:
|
213
|
+
# Something went wrong while parsing the output from pip.
|
214
|
+
# This may happen if pip is not correctly installed.
|
215
|
+
return {}
|
216
|
+
|
217
|
+
|
218
|
+
class PackageInfo(NamedTuple):
|
219
|
+
"""Represents information about a package."""
|
220
|
+
name: str
|
221
|
+
type: Optional[str] = None
|
222
|
+
source: Optional[str] = None
|
223
|
+
installed_version: Optional[str] = None
|
224
|
+
remote_version: Optional[str] = None
|
225
|
+
|
226
|
+
def is_installed(self) -> bool:
|
227
|
+
r"""
|
228
|
+
Whether the package is installed in the system.
|
229
|
+
"""
|
230
|
+
return self.installed_version is not None
|
231
|
+
|
232
|
+
|
233
|
+
def list_packages(*pkg_types: str, pkg_sources: list[str] = ['normal', 'pip', 'script'],
|
234
|
+
local: bool = False, ignore_URLError: bool = False, exclude_pip: bool = False) -> dict[str, PackageInfo]:
|
235
|
+
r"""
|
236
|
+
Return a dictionary of information about each package.
|
237
|
+
|
238
|
+
The keys are package names and values are named tuples with the following keys:
|
239
|
+
|
240
|
+
- ``'type'`` -- either ``'base``, ``'standard'``, ``'optional'``, or ``'experimental'``
|
241
|
+
- ``'source'`` -- either ``'normal', ``'pip'``, or ``'script'``
|
242
|
+
- ``'installed'`` -- boolean
|
243
|
+
- ``'installed_version'`` -- ``None`` or a string
|
244
|
+
- ``'remote_version'`` -- string
|
245
|
+
|
246
|
+
INPUT:
|
247
|
+
|
248
|
+
- ``pkg_types`` -- (optional) a sublist of ``'base``, ``'standard'``, ``'optional'``,
|
249
|
+
or ``'experimental'``. If provided, list only the packages with the
|
250
|
+
given type(s), otherwise list all packages.
|
251
|
+
|
252
|
+
- ``pkg_sources`` -- (optional) a sublist of ``'normal', ``'pip'``, or ``'script'``.
|
253
|
+
If provided, list only the packages with the given source(s), otherwise list all
|
254
|
+
packages.
|
255
|
+
|
256
|
+
- ``local`` -- boolean (default: ``False``); if set to ``True``, then do not
|
257
|
+
consult remote (PyPI) repositories for package versions (only applicable for
|
258
|
+
``'pip'`` type)
|
259
|
+
|
260
|
+
- ``exclude_pip`` -- boolean (default: ``False``); if set to ``True``, then
|
261
|
+
pip packages are not considered. This is the same as removing ``'pip'``
|
262
|
+
from ``pkg_sources``
|
263
|
+
|
264
|
+
- ``ignore_URLError`` -- boolean (default: ``False``); if set to ``True``, then
|
265
|
+
connection errors will be ignored
|
266
|
+
|
267
|
+
EXAMPLES::
|
268
|
+
|
269
|
+
sage: # optional - sage_spkg
|
270
|
+
sage: from sage.misc.package import list_packages
|
271
|
+
sage: L = list_packages('standard')
|
272
|
+
sage: sorted(L.keys()) # random
|
273
|
+
['alabaster',
|
274
|
+
'babel',
|
275
|
+
...
|
276
|
+
'zlib']
|
277
|
+
sage: sage_conf_info = L['sage_conf']
|
278
|
+
sage: sage_conf_info.type
|
279
|
+
'standard'
|
280
|
+
sage: sage_conf_info.is_installed()
|
281
|
+
True
|
282
|
+
sage: sage_conf_info.source
|
283
|
+
'script'
|
284
|
+
|
285
|
+
sage: # optional - sage_spkg internet
|
286
|
+
sage: L = list_packages(pkg_sources=['pip'], local=True)
|
287
|
+
sage: bp_info = L['biopython']
|
288
|
+
sage: bp_info.type
|
289
|
+
'optional'
|
290
|
+
sage: bp_info.source
|
291
|
+
'pip'
|
292
|
+
|
293
|
+
Check the option ``exclude_pip``::
|
294
|
+
|
295
|
+
sage: [p for p, d in list_packages('optional', exclude_pip=True).items() # optional - sage_spkg
|
296
|
+
....: if d.source == 'pip']
|
297
|
+
[]
|
298
|
+
"""
|
299
|
+
if not pkg_types:
|
300
|
+
pkg_types = ('base', 'standard', 'optional', 'experimental')
|
301
|
+
elif any(pkg_type not in ('base', 'standard', 'optional', 'experimental') for pkg_type in pkg_types):
|
302
|
+
raise ValueError("Each pkg_type must be one of 'base', 'standard', 'optional', 'experimental'")
|
303
|
+
|
304
|
+
if exclude_pip:
|
305
|
+
pkg_sources = [s for s in pkg_sources if s != 'pip']
|
306
|
+
|
307
|
+
pkgs = {p: PackageInfo(name=p, installed_version=v)
|
308
|
+
for p, v in installed_packages('pip' not in pkg_sources).items()}
|
309
|
+
|
310
|
+
# Add additional information based on Sage's package repository
|
311
|
+
lp = []
|
312
|
+
SAGE_PKGS = sage.env.SAGE_PKGS
|
313
|
+
if not SAGE_PKGS:
|
314
|
+
return pkgs
|
315
|
+
|
316
|
+
try:
|
317
|
+
lp = os.listdir(SAGE_PKGS)
|
318
|
+
except FileNotFoundError:
|
319
|
+
return pkgs
|
320
|
+
|
321
|
+
for p in lp:
|
322
|
+
|
323
|
+
typ = spkg_type(p)
|
324
|
+
if not typ:
|
325
|
+
continue
|
326
|
+
|
327
|
+
if os.path.isfile(os.path.join(SAGE_PKGS, p, "requirements.txt")):
|
328
|
+
src = 'pip'
|
329
|
+
elif os.path.isfile(os.path.join(SAGE_PKGS, p, "checksums.ini")):
|
330
|
+
src = 'normal'
|
331
|
+
else:
|
332
|
+
src = 'script'
|
333
|
+
|
334
|
+
if typ not in pkg_types or src not in pkg_sources:
|
335
|
+
try:
|
336
|
+
del pkgs[p]
|
337
|
+
except KeyError:
|
338
|
+
pass
|
339
|
+
continue
|
340
|
+
|
341
|
+
if src == 'pip':
|
342
|
+
if not local:
|
343
|
+
remote_version = pip_remote_version(p, ignore_URLError=ignore_URLError)
|
344
|
+
else:
|
345
|
+
remote_version = None
|
346
|
+
elif src == 'normal':
|
347
|
+
# If package-version.txt does not exist, that is an error
|
348
|
+
# in the build system => we just propagate the exception
|
349
|
+
package_filename = os.path.join(SAGE_PKGS, p, "package-version.txt")
|
350
|
+
with open(package_filename) as f:
|
351
|
+
remote_version = f.read().strip()
|
352
|
+
else:
|
353
|
+
remote_version = None
|
354
|
+
|
355
|
+
pkg = pkgs.get(p, PackageInfo(name=p))
|
356
|
+
pkgs[p] = PackageInfo(p, typ, src, pkg.installed_version, remote_version)
|
357
|
+
|
358
|
+
return pkgs
|
359
|
+
|
360
|
+
|
361
|
+
def _spkg_inst_dirs():
|
362
|
+
"""
|
363
|
+
Generator for the installation manifest directories as resolved paths.
|
364
|
+
|
365
|
+
It yields first ``SAGE_LOCAL_SPKG_INST``, then ``SAGE_VENV_SPKG_INST``,
|
366
|
+
if defined; but it both resolve to the same directory, it only yields
|
367
|
+
one element.
|
368
|
+
|
369
|
+
EXAMPLES::
|
370
|
+
|
371
|
+
sage: from sage.misc.package import _spkg_inst_dirs
|
372
|
+
sage: list(_spkg_inst_dirs())
|
373
|
+
[...]
|
374
|
+
"""
|
375
|
+
last_inst_dir = None
|
376
|
+
for inst_dir in (sage.env.SAGE_LOCAL_SPKG_INST, sage.env.SAGE_VENV_SPKG_INST):
|
377
|
+
if inst_dir:
|
378
|
+
inst_dir = Path(inst_dir).resolve()
|
379
|
+
if inst_dir.is_dir() and inst_dir != last_inst_dir:
|
380
|
+
yield inst_dir
|
381
|
+
last_inst_dir = inst_dir
|
382
|
+
|
383
|
+
|
384
|
+
def installed_packages(exclude_pip=True):
|
385
|
+
"""
|
386
|
+
Return a dictionary of all installed packages, with version numbers.
|
387
|
+
|
388
|
+
INPUT:
|
389
|
+
|
390
|
+
- ``exclude_pip`` -- boolean (default: ``True``); whether "pip" packages
|
391
|
+
are excluded from the list
|
392
|
+
|
393
|
+
EXAMPLES:
|
394
|
+
|
395
|
+
Below we test for a standard package without ``spkg-configure.m4`` script
|
396
|
+
that should be installed in ``SAGE_LOCAL``. When Sage is installed by
|
397
|
+
the Sage distribution (indicated by feature ``sage_spkg``), we should have
|
398
|
+
the installation record for this package. (We do not test for installation
|
399
|
+
records of Python packages. Our ``SAGE_VENV`` is not necessarily the
|
400
|
+
main Sage venv; it could be a user-created venv or a venv created by tox.)::
|
401
|
+
|
402
|
+
sage: # optional - sage_spkg
|
403
|
+
sage: from sage.misc.package import installed_packages
|
404
|
+
sage: sorted(installed_packages().keys())
|
405
|
+
[...'gnulib', ...]
|
406
|
+
sage: installed_packages()['gnulib'] # random
|
407
|
+
'f9b39c4e337f1dc0dd07c4f3985c476fb875d799'
|
408
|
+
|
409
|
+
.. SEEALSO::
|
410
|
+
|
411
|
+
:func:`sage.misc.package.list_packages`
|
412
|
+
"""
|
413
|
+
installed = {}
|
414
|
+
if not exclude_pip:
|
415
|
+
installed.update(pip_installed_packages(normalization='spkg'))
|
416
|
+
# Sage packages should override pip packages (Issue #23997)
|
417
|
+
|
418
|
+
for inst_dir in _spkg_inst_dirs():
|
419
|
+
try:
|
420
|
+
lp = os.listdir(inst_dir)
|
421
|
+
installed.update(pkgname_split(pkgname) for pkgname in lp
|
422
|
+
if not pkgname.startswith('.'))
|
423
|
+
except FileNotFoundError:
|
424
|
+
pass
|
425
|
+
return installed
|
426
|
+
|
427
|
+
|
428
|
+
def is_package_installed(package, exclude_pip=True):
|
429
|
+
"""
|
430
|
+
Return whether (any version of) ``package`` is installed.
|
431
|
+
|
432
|
+
INPUT:
|
433
|
+
|
434
|
+
- ``package`` -- the name of the package
|
435
|
+
|
436
|
+
- ``exclude_pip`` -- boolean (default: ``True``); whether to consider pip
|
437
|
+
type packages
|
438
|
+
|
439
|
+
EXAMPLES::
|
440
|
+
|
441
|
+
sage: from sage.misc.package import is_package_installed
|
442
|
+
sage: is_package_installed('gnulib') # optional - sage_spkg
|
443
|
+
True
|
444
|
+
|
445
|
+
Giving just the beginning of the package name is not good enough::
|
446
|
+
|
447
|
+
sage: is_package_installed('conway_poly') # optional - sage_spkg
|
448
|
+
False
|
449
|
+
|
450
|
+
Otherwise, installing "pillow" would cause this function to think
|
451
|
+
that "pil" is installed, for example.
|
452
|
+
|
453
|
+
.. NOTE::
|
454
|
+
|
455
|
+
Do not use this function to check whether you can use a feature from an
|
456
|
+
external library. This only checks whether something was installed with
|
457
|
+
``sage -i`` but it may have been installed by other means (for example
|
458
|
+
if this copy of Sage has been installed as part of a distribution.)
|
459
|
+
Use the framework provided by :mod:`sage.features` to check
|
460
|
+
whether a library is installed and functional.
|
461
|
+
"""
|
462
|
+
return any(p == package for p in installed_packages(exclude_pip))
|
463
|
+
|
464
|
+
|
465
|
+
def is_package_installed_and_updated(package: str) -> bool:
|
466
|
+
r"""
|
467
|
+
Return whether the given package is installed and up-to-date.
|
468
|
+
|
469
|
+
INPUT:
|
470
|
+
|
471
|
+
- ``package`` -- the name of the package
|
472
|
+
|
473
|
+
EXAMPLES::
|
474
|
+
|
475
|
+
sage: from sage.misc.package import is_package_installed_and_updated
|
476
|
+
sage: is_package_installed_and_updated("alabaster") # optional - build, random
|
477
|
+
False
|
478
|
+
"""
|
479
|
+
try:
|
480
|
+
all_packages = list_packages(local=True)
|
481
|
+
pkginfo = all_packages[package]
|
482
|
+
return pkginfo.installed_version == pkginfo.remote_version
|
483
|
+
except KeyError:
|
484
|
+
# Might be an installed old-style package
|
485
|
+
return is_package_installed(package)
|
486
|
+
|
487
|
+
|
488
|
+
def package_versions(package_type, local=False):
|
489
|
+
r"""
|
490
|
+
Return version information for each Sage package.
|
491
|
+
|
492
|
+
INPUT:
|
493
|
+
|
494
|
+
- ``package_type`` -- string; one of ``'standard'``, ``'optional'`` or
|
495
|
+
``'experimental'``
|
496
|
+
|
497
|
+
- ``local`` -- boolean (default: ``False``); only query local data (no internet needed)
|
498
|
+
|
499
|
+
For packages of the given type, return a dictionary whose entries
|
500
|
+
are of the form ``'package': (installed, latest)``, where
|
501
|
+
``installed`` is the installed version (or ``None`` if not
|
502
|
+
installed) and ``latest`` is the latest available version. If the
|
503
|
+
package has a directory in ``SAGE_ROOT/build/pkgs/``, then
|
504
|
+
``latest`` is determined by the file ``package-version.txt`` in
|
505
|
+
that directory. If ``local`` is ``False``, then Sage's servers are
|
506
|
+
queried for package information.
|
507
|
+
|
508
|
+
.. SEEALSO:: :func:`sage.misc.package.list_packages`
|
509
|
+
|
510
|
+
EXAMPLES::
|
511
|
+
|
512
|
+
sage: # optional - sage_spkg
|
513
|
+
sage: from sage.misc.package import package_versions
|
514
|
+
sage: std = package_versions('standard', local=True)
|
515
|
+
sage: 'gap' in std
|
516
|
+
True
|
517
|
+
sage: std['zlib'] # random
|
518
|
+
('1.2.11.p0', '1.2.11.p0')
|
519
|
+
"""
|
520
|
+
return {pkg.name: (pkg.installed_version, pkg.remote_version) for pkg in list_packages(package_type, local=local).values()}
|
521
|
+
|
522
|
+
|
523
|
+
def package_manifest(package):
|
524
|
+
"""
|
525
|
+
Return the manifest for ``package``.
|
526
|
+
|
527
|
+
INPUT:
|
528
|
+
|
529
|
+
- ``package`` -- package name
|
530
|
+
|
531
|
+
The manifest is written in the file
|
532
|
+
``SAGE_SPKG_INST/package-VERSION``. It is a JSON file containing a
|
533
|
+
dictionary with the package name, version, installation date, list
|
534
|
+
of installed files, etc.
|
535
|
+
|
536
|
+
EXAMPLES::
|
537
|
+
|
538
|
+
sage: # optional - sage_spkg
|
539
|
+
sage: from sage.misc.package import package_manifest
|
540
|
+
sage: manifest = package_manifest('gnulib')
|
541
|
+
sage: manifest['package_name'] == 'gnulib'
|
542
|
+
True
|
543
|
+
sage: 'files' in manifest
|
544
|
+
True
|
545
|
+
|
546
|
+
Test a nonexistent package::
|
547
|
+
|
548
|
+
sage: package_manifest('dummy-package') # optional - sage_spkg
|
549
|
+
Traceback (most recent call last):
|
550
|
+
...
|
551
|
+
KeyError: 'dummy-package'
|
552
|
+
"""
|
553
|
+
version = installed_packages()[package]
|
554
|
+
for inst_dir in _spkg_inst_dirs():
|
555
|
+
stamp_file = os.path.join(inst_dir,
|
556
|
+
'{}-{}'.format(package, version))
|
557
|
+
try:
|
558
|
+
with open(stamp_file) as f:
|
559
|
+
return json.load(f)
|
560
|
+
except FileNotFoundError:
|
561
|
+
pass
|
562
|
+
raise RuntimeError('package manifest directory changed at runtime')
|
563
|
+
|
564
|
+
|
565
|
+
# PackageNotFoundError used to be an exception class.
|
566
|
+
# It was deprecated in #30607 and removed afterwards.
|
567
|
+
# User code can continue to use PackageNotFoundError in
|
568
|
+
# try...except statements using this definition, which
|
569
|
+
# catches no exception.
|
570
|
+
PackageNotFoundError = ()
|