passagemath-objects 10.6.47__cp311-cp311-macosx_13_0_x86_64.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_objects/.dylibs/libgmp.10.dylib +0 -0
- passagemath_objects/__init__.py +3 -0
- passagemath_objects-10.6.47.dist-info/METADATA +115 -0
- passagemath_objects-10.6.47.dist-info/RECORD +280 -0
- passagemath_objects-10.6.47.dist-info/WHEEL +6 -0
- passagemath_objects-10.6.47.dist-info/top_level.txt +3 -0
- sage/all__sagemath_objects.py +37 -0
- sage/arith/all__sagemath_objects.py +5 -0
- sage/arith/long.pxd +411 -0
- sage/arith/numerical_approx.cpython-311-darwin.so +0 -0
- sage/arith/numerical_approx.pxd +35 -0
- sage/arith/numerical_approx.pyx +75 -0
- sage/arith/power.cpython-311-darwin.so +0 -0
- sage/arith/power.pxd +31 -0
- sage/arith/power.pyx +127 -0
- sage/categories/action.cpython-311-darwin.so +0 -0
- sage/categories/action.pxd +29 -0
- sage/categories/action.pyx +641 -0
- sage/categories/algebra_functor.py +745 -0
- sage/categories/all__sagemath_objects.py +33 -0
- sage/categories/basic.py +62 -0
- sage/categories/cartesian_product.py +295 -0
- sage/categories/category.py +3401 -0
- sage/categories/category_cy_helper.cpython-311-darwin.so +0 -0
- sage/categories/category_cy_helper.pxd +8 -0
- sage/categories/category_cy_helper.pyx +322 -0
- sage/categories/category_singleton.cpython-311-darwin.so +0 -0
- sage/categories/category_singleton.pxd +3 -0
- sage/categories/category_singleton.pyx +342 -0
- sage/categories/category_types.py +637 -0
- sage/categories/category_with_axiom.py +2876 -0
- sage/categories/covariant_functorial_construction.py +703 -0
- sage/categories/facade_sets.py +228 -0
- sage/categories/functor.cpython-311-darwin.so +0 -0
- sage/categories/functor.pxd +7 -0
- sage/categories/functor.pyx +691 -0
- sage/categories/homset.py +1338 -0
- sage/categories/homsets.py +364 -0
- sage/categories/isomorphic_objects.py +73 -0
- sage/categories/map.cpython-311-darwin.so +0 -0
- sage/categories/map.pxd +34 -0
- sage/categories/map.pyx +2106 -0
- sage/categories/morphism.cpython-311-darwin.so +0 -0
- sage/categories/morphism.pxd +14 -0
- sage/categories/morphism.pyx +895 -0
- sage/categories/objects.py +167 -0
- sage/categories/primer.py +1696 -0
- sage/categories/pushout.py +4834 -0
- sage/categories/quotients.py +64 -0
- sage/categories/realizations.py +200 -0
- sage/categories/sets_cat.py +3290 -0
- sage/categories/sets_with_partial_maps.py +52 -0
- sage/categories/subobjects.py +64 -0
- sage/categories/subquotients.py +21 -0
- sage/categories/with_realizations.py +311 -0
- sage/cpython/__init__.py +19 -0
- sage/cpython/_py2_random.py +619 -0
- sage/cpython/all.py +3 -0
- sage/cpython/atexit.cpython-311-darwin.so +0 -0
- sage/cpython/atexit.pyx +269 -0
- sage/cpython/builtin_types.cpython-311-darwin.so +0 -0
- sage/cpython/builtin_types.pyx +7 -0
- sage/cpython/cython_metaclass.cpython-311-darwin.so +0 -0
- sage/cpython/cython_metaclass.h +117 -0
- sage/cpython/cython_metaclass.pxd +3 -0
- sage/cpython/cython_metaclass.pyx +130 -0
- sage/cpython/debug.cpython-311-darwin.so +0 -0
- sage/cpython/debug.pyx +302 -0
- sage/cpython/dict_del_by_value.cpython-311-darwin.so +0 -0
- sage/cpython/dict_del_by_value.pxd +9 -0
- sage/cpython/dict_del_by_value.pyx +191 -0
- sage/cpython/dict_internal.h +245 -0
- sage/cpython/getattr.cpython-311-darwin.so +0 -0
- sage/cpython/getattr.pxd +9 -0
- sage/cpython/getattr.pyx +439 -0
- sage/cpython/pycore_long.h +97 -0
- sage/cpython/pycore_long.pxd +10 -0
- sage/cpython/python_debug.h +44 -0
- sage/cpython/python_debug.pxd +47 -0
- sage/cpython/pyx_visit.h +13 -0
- sage/cpython/string.cpython-311-darwin.so +0 -0
- sage/cpython/string.pxd +76 -0
- sage/cpython/string.pyx +34 -0
- sage/cpython/string_impl.h +60 -0
- sage/cpython/type.cpython-311-darwin.so +0 -0
- sage/cpython/type.pxd +2 -0
- sage/cpython/type.pyx +40 -0
- sage/cpython/wrapperdescr.pxd +67 -0
- sage/ext/all__sagemath_objects.py +3 -0
- sage/ext/ccobject.h +64 -0
- sage/ext/cplusplus.pxd +17 -0
- sage/ext/mod_int.h +30 -0
- sage/ext/mod_int.pxd +24 -0
- sage/ext/stdsage.pxd +39 -0
- sage/groups/all__sagemath_objects.py +1 -0
- sage/groups/group.cpython-311-darwin.so +0 -0
- sage/groups/group.pxd +14 -0
- sage/groups/group.pyx +322 -0
- sage/groups/old.cpython-311-darwin.so +0 -0
- sage/groups/old.pxd +14 -0
- sage/groups/old.pyx +219 -0
- sage/libs/all__sagemath_objects.py +3 -0
- sage/libs/gmp/__init__.py +1 -0
- sage/libs/gmp/all.pxd +6 -0
- sage/libs/gmp/binop.pxd +23 -0
- sage/libs/gmp/misc.pxd +8 -0
- sage/libs/gmp/mpf.pxd +88 -0
- sage/libs/gmp/mpn.pxd +57 -0
- sage/libs/gmp/mpq.pxd +57 -0
- sage/libs/gmp/mpz.pxd +202 -0
- sage/libs/gmp/pylong.cpython-311-darwin.so +0 -0
- sage/libs/gmp/pylong.pxd +12 -0
- sage/libs/gmp/pylong.pyx +150 -0
- sage/libs/gmp/random.pxd +25 -0
- sage/libs/gmp/randomize.pxd +59 -0
- sage/libs/gmp/types.pxd +53 -0
- sage/libs/gmpxx.pxd +19 -0
- sage/misc/abstract_method.py +276 -0
- sage/misc/all__sagemath_objects.py +43 -0
- sage/misc/bindable_class.py +253 -0
- sage/misc/c3_controlled.cpython-311-darwin.so +0 -0
- sage/misc/c3_controlled.pxd +2 -0
- sage/misc/c3_controlled.pyx +1402 -0
- sage/misc/cachefunc.cpython-311-darwin.so +0 -0
- sage/misc/cachefunc.pxd +43 -0
- sage/misc/cachefunc.pyx +3781 -0
- sage/misc/call.py +188 -0
- sage/misc/classcall_metaclass.cpython-311-darwin.so +0 -0
- sage/misc/classcall_metaclass.pxd +14 -0
- sage/misc/classcall_metaclass.pyx +599 -0
- sage/misc/constant_function.cpython-311-darwin.so +0 -0
- sage/misc/constant_function.pyx +130 -0
- sage/misc/decorators.py +747 -0
- sage/misc/fast_methods.cpython-311-darwin.so +0 -0
- sage/misc/fast_methods.pxd +20 -0
- sage/misc/fast_methods.pyx +351 -0
- sage/misc/flatten.py +90 -0
- sage/misc/fpickle.cpython-311-darwin.so +0 -0
- sage/misc/fpickle.pyx +177 -0
- sage/misc/function_mangling.cpython-311-darwin.so +0 -0
- sage/misc/function_mangling.pxd +11 -0
- sage/misc/function_mangling.pyx +308 -0
- sage/misc/inherit_comparison.cpython-311-darwin.so +0 -0
- sage/misc/inherit_comparison.pxd +5 -0
- sage/misc/inherit_comparison.pyx +105 -0
- sage/misc/instancedoc.cpython-311-darwin.so +0 -0
- sage/misc/instancedoc.pyx +331 -0
- sage/misc/lazy_attribute.cpython-311-darwin.so +0 -0
- sage/misc/lazy_attribute.pyx +607 -0
- sage/misc/lazy_format.py +135 -0
- sage/misc/lazy_import.cpython-311-darwin.so +0 -0
- sage/misc/lazy_import.pyx +1299 -0
- sage/misc/lazy_import_cache.py +36 -0
- sage/misc/lazy_list.cpython-311-darwin.so +0 -0
- sage/misc/lazy_list.pxd +19 -0
- sage/misc/lazy_list.pyx +1187 -0
- sage/misc/lazy_string.cpython-311-darwin.so +0 -0
- sage/misc/lazy_string.pxd +7 -0
- sage/misc/lazy_string.pyx +546 -0
- sage/misc/misc.py +1066 -0
- sage/misc/misc_c.cpython-311-darwin.so +0 -0
- sage/misc/misc_c.pxd +3 -0
- sage/misc/misc_c.pyx +766 -0
- sage/misc/namespace_package.py +37 -0
- sage/misc/nested_class.cpython-311-darwin.so +0 -0
- sage/misc/nested_class.pxd +3 -0
- sage/misc/nested_class.pyx +394 -0
- sage/misc/persist.cpython-311-darwin.so +0 -0
- sage/misc/persist.pyx +1251 -0
- sage/misc/prandom.py +418 -0
- sage/misc/randstate.cpython-311-darwin.so +0 -0
- sage/misc/randstate.pxd +30 -0
- sage/misc/randstate.pyx +1059 -0
- sage/misc/repr.py +203 -0
- sage/misc/reset.cpython-311-darwin.so +0 -0
- sage/misc/reset.pyx +196 -0
- sage/misc/sage_ostools.cpython-311-darwin.so +0 -0
- sage/misc/sage_ostools.pyx +323 -0
- sage/misc/sage_timeit.py +275 -0
- sage/misc/sage_timeit_class.cpython-311-darwin.so +0 -0
- sage/misc/sage_timeit_class.pyx +120 -0
- sage/misc/sage_unittest.py +637 -0
- sage/misc/sageinspect.py +2768 -0
- sage/misc/session.cpython-311-darwin.so +0 -0
- sage/misc/session.pyx +392 -0
- sage/misc/superseded.py +557 -0
- sage/misc/test_nested_class.py +228 -0
- sage/misc/timing.py +264 -0
- sage/misc/unknown.py +222 -0
- sage/misc/verbose.py +253 -0
- sage/misc/weak_dict.cpython-311-darwin.so +0 -0
- sage/misc/weak_dict.pxd +15 -0
- sage/misc/weak_dict.pyx +1231 -0
- sage/modules/all__sagemath_objects.py +1 -0
- sage/modules/module.cpython-311-darwin.so +0 -0
- sage/modules/module.pxd +5 -0
- sage/modules/module.pyx +329 -0
- sage/rings/all__sagemath_objects.py +3 -0
- sage/rings/integer_fake.h +22 -0
- sage/rings/integer_fake.pxd +55 -0
- sage/sets/all__sagemath_objects.py +3 -0
- sage/sets/pythonclass.cpython-311-darwin.so +0 -0
- sage/sets/pythonclass.pxd +9 -0
- sage/sets/pythonclass.pyx +247 -0
- sage/structure/__init__.py +4 -0
- sage/structure/all.py +30 -0
- sage/structure/category_object.cpython-311-darwin.so +0 -0
- sage/structure/category_object.pxd +28 -0
- sage/structure/category_object.pyx +1087 -0
- sage/structure/coerce.cpython-311-darwin.so +0 -0
- sage/structure/coerce.pxd +44 -0
- sage/structure/coerce.pyx +2107 -0
- sage/structure/coerce_actions.cpython-311-darwin.so +0 -0
- sage/structure/coerce_actions.pxd +27 -0
- sage/structure/coerce_actions.pyx +988 -0
- sage/structure/coerce_dict.cpython-311-darwin.so +0 -0
- sage/structure/coerce_dict.pxd +51 -0
- sage/structure/coerce_dict.pyx +1557 -0
- sage/structure/coerce_exceptions.py +23 -0
- sage/structure/coerce_maps.cpython-311-darwin.so +0 -0
- sage/structure/coerce_maps.pxd +28 -0
- sage/structure/coerce_maps.pyx +718 -0
- sage/structure/debug_options.cpython-311-darwin.so +0 -0
- sage/structure/debug_options.pxd +6 -0
- sage/structure/debug_options.pyx +54 -0
- sage/structure/dynamic_class.py +541 -0
- sage/structure/element.cpython-311-darwin.so +0 -0
- sage/structure/element.pxd +272 -0
- sage/structure/element.pyx +4772 -0
- sage/structure/element_wrapper.cpython-311-darwin.so +0 -0
- sage/structure/element_wrapper.pxd +12 -0
- sage/structure/element_wrapper.pyx +582 -0
- sage/structure/factorization.py +1422 -0
- sage/structure/factorization_integer.py +105 -0
- sage/structure/factory.cpython-311-darwin.so +0 -0
- sage/structure/factory.pyx +786 -0
- sage/structure/formal_sum.py +489 -0
- sage/structure/gens_py.py +73 -0
- sage/structure/global_options.py +1743 -0
- sage/structure/indexed_generators.py +863 -0
- sage/structure/list_clone.cpython-311-darwin.so +0 -0
- sage/structure/list_clone.pxd +65 -0
- sage/structure/list_clone.pyx +1867 -0
- sage/structure/list_clone_demo.cpython-311-darwin.so +0 -0
- sage/structure/list_clone_demo.pyx +248 -0
- sage/structure/list_clone_timings.py +179 -0
- sage/structure/list_clone_timings_cy.cpython-311-darwin.so +0 -0
- sage/structure/list_clone_timings_cy.pyx +86 -0
- sage/structure/mutability.cpython-311-darwin.so +0 -0
- sage/structure/mutability.pxd +21 -0
- sage/structure/mutability.pyx +348 -0
- sage/structure/nonexact.py +69 -0
- sage/structure/parent.cpython-311-darwin.so +0 -0
- sage/structure/parent.pxd +112 -0
- sage/structure/parent.pyx +3093 -0
- sage/structure/parent_base.cpython-311-darwin.so +0 -0
- sage/structure/parent_base.pxd +13 -0
- sage/structure/parent_base.pyx +44 -0
- sage/structure/parent_gens.cpython-311-darwin.so +0 -0
- sage/structure/parent_gens.pxd +22 -0
- sage/structure/parent_gens.pyx +377 -0
- sage/structure/parent_old.cpython-311-darwin.so +0 -0
- sage/structure/parent_old.pxd +25 -0
- sage/structure/parent_old.pyx +294 -0
- sage/structure/proof/__init__.py +1 -0
- sage/structure/proof/all.py +243 -0
- sage/structure/proof/proof.py +300 -0
- sage/structure/richcmp.cpython-311-darwin.so +0 -0
- sage/structure/richcmp.pxd +213 -0
- sage/structure/richcmp.pyx +495 -0
- sage/structure/sage_object.cpython-311-darwin.so +0 -0
- sage/structure/sage_object.pxd +3 -0
- sage/structure/sage_object.pyx +988 -0
- sage/structure/sage_object_test.py +19 -0
- sage/structure/sequence.py +937 -0
- sage/structure/set_factories.py +1178 -0
- sage/structure/set_factories_example.py +527 -0
- sage/structure/support_view.py +179 -0
- sage/structure/test_factory.py +56 -0
- sage/structure/unique_representation.py +1359 -0
sage/misc/persist.pyx
ADDED
|
@@ -0,0 +1,1251 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-objects
|
|
2
|
+
# cython: old_style_globals=True
|
|
3
|
+
# The old_style_globals directive is important for load() to work correctly.
|
|
4
|
+
# However, this should be removed in favor of user_globals; see
|
|
5
|
+
# https://github.com/sagemath/sage/issues/18083
|
|
6
|
+
|
|
7
|
+
r"""
|
|
8
|
+
Object persistence
|
|
9
|
+
|
|
10
|
+
You can load and save most Sage object to disk using the load and
|
|
11
|
+
save member functions and commands.
|
|
12
|
+
|
|
13
|
+
.. NOTE::
|
|
14
|
+
|
|
15
|
+
It is impossible to save certain Sage objects to disk. For example,
|
|
16
|
+
if `x` is a MAGMA object, i.e., a wrapper around an object
|
|
17
|
+
that is defined in MAGMA, there is no way to save `x` to
|
|
18
|
+
disk, since MAGMA doesn't support saving of individual objects to
|
|
19
|
+
disk.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
- Versions: Loading and saving of objects is guaranteed to work
|
|
23
|
+
even if the version of Python changes. Saved objects can be loaded
|
|
24
|
+
in future versions of Python. However, if the data structure that
|
|
25
|
+
defines the object, e.g., in Sage code, changes drastically (or
|
|
26
|
+
changes name or disappears), then the object might not load
|
|
27
|
+
correctly or work correctly.
|
|
28
|
+
|
|
29
|
+
- Objects are zlib compressed for space efficiency.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import io
|
|
33
|
+
import os
|
|
34
|
+
import pickle
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
from textwrap import dedent
|
|
38
|
+
|
|
39
|
+
# change to import zlib to use zlib instead; but this
|
|
40
|
+
# slows down loading any data stored in the other format
|
|
41
|
+
import zlib
|
|
42
|
+
import bz2
|
|
43
|
+
comp = zlib
|
|
44
|
+
comp_other = bz2
|
|
45
|
+
|
|
46
|
+
from sage.misc.sage_unittest import TestSuite
|
|
47
|
+
from sage.misc.superseded import deprecation
|
|
48
|
+
|
|
49
|
+
# We define two global dictionaries `already_pickled` and
|
|
50
|
+
# `already_unpickled`, which are intended to help you to implement
|
|
51
|
+
# pickling when cyclic definitions could happen.
|
|
52
|
+
#
|
|
53
|
+
# You have the guarantee that the dictionary `already_pickled`
|
|
54
|
+
# (resp. `already_unpickled`) will be cleared after the complete
|
|
55
|
+
# pickling (resp. unpickling) process has been completed (and not
|
|
56
|
+
# before!).
|
|
57
|
+
# Apart from this, you are free to use these variables as you like.
|
|
58
|
+
#
|
|
59
|
+
# However, the standard utilisation is the following.
|
|
60
|
+
# The pickling method (namely ``__reduce__``) checks if the id of the
|
|
61
|
+
# current element appears in the dictionary `already_pickled`. If it
|
|
62
|
+
# does not, the methods records that this element is about to be
|
|
63
|
+
# pickled by adding the entry { id: True } to `already_pickled`.
|
|
64
|
+
# In all cases, the pickling method pickles the element and includes
|
|
65
|
+
# the id in the tuple of arguments that will be passed in afterwards
|
|
66
|
+
# to the unpickling function.
|
|
67
|
+
# The unpickling function then receives all the necessary information
|
|
68
|
+
# to reconstruct the element, together with an id.
|
|
69
|
+
# If this id appears as a key of the dictionary `already_unpickled`, it
|
|
70
|
+
# returns the corresponding value.
|
|
71
|
+
# Otherwise, it builds the element, adds the entry { id: element } to
|
|
72
|
+
# `already_unpickled` and finally returns the element.
|
|
73
|
+
#
|
|
74
|
+
# For a working example, see sage.rings.padics.lazy_template.LazyElement_unknown
|
|
75
|
+
already_pickled = {}
|
|
76
|
+
already_unpickled = {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
cdef _normalize_filename(s):
|
|
80
|
+
"""
|
|
81
|
+
Append the .sobj extension to a filename if it doesn't already have it.
|
|
82
|
+
"""
|
|
83
|
+
if s[-5:] != '.sobj':
|
|
84
|
+
return s + '.sobj'
|
|
85
|
+
|
|
86
|
+
return s
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def load(*filename, compress=True, verbose=True, **kwargs):
|
|
90
|
+
r"""
|
|
91
|
+
Load Sage object from the file with name filename, which will have
|
|
92
|
+
an ``.sobj`` extension added if it doesn't have one. Or, if the input
|
|
93
|
+
is a filename ending in ``.py``, ``.pyx``, ``.sage``, ``.spyx``,
|
|
94
|
+
``.f``, ``.f90`` or ``.m``, load that file into the current running
|
|
95
|
+
session using :func:`sage.repl.load.load`.
|
|
96
|
+
|
|
97
|
+
Loaded files are not loaded into their own namespace, i.e., this is
|
|
98
|
+
much more like Python's ``execfile`` than Python's ``import``.
|
|
99
|
+
|
|
100
|
+
This function also loads a ``.sobj`` file over a network by
|
|
101
|
+
specifying the full URL. (Setting ``verbose = False`` suppresses
|
|
102
|
+
the loading progress indicator.)
|
|
103
|
+
|
|
104
|
+
When a pickle created with Python 2 is unpickled in Python 3, Sage
|
|
105
|
+
uses the default encoding ``latin1`` to unpickle data of type :class:`str`.
|
|
106
|
+
|
|
107
|
+
Finally, if you give multiple positional input arguments, then all
|
|
108
|
+
of those files are loaded, or all of the objects are loaded and a
|
|
109
|
+
list of the corresponding loaded objects is returned.
|
|
110
|
+
|
|
111
|
+
If ``compress`` is true (the default), then the data stored in the file
|
|
112
|
+
are supposed to be compressed. If ``verbose`` is true (the default), then
|
|
113
|
+
some logging is printed when accessing remote files. Further keyword
|
|
114
|
+
arguments are passed to :func:`pickle.load`.
|
|
115
|
+
|
|
116
|
+
EXAMPLES::
|
|
117
|
+
|
|
118
|
+
sage: u = 'https://www.sagemath.org/files/test.sobj'
|
|
119
|
+
sage: s = load(u) # optional - internet
|
|
120
|
+
Attempting to load remote file: https://www.sagemath.org/files/test.sobj
|
|
121
|
+
Loading started
|
|
122
|
+
Loading ended
|
|
123
|
+
sage: s # optional - internet
|
|
124
|
+
'hello SageMath'
|
|
125
|
+
|
|
126
|
+
We test loading a file or multiple files or even mixing loading files and objects::
|
|
127
|
+
|
|
128
|
+
sage: t = tmp_filename(ext='.py')
|
|
129
|
+
sage: with open(t, 'w') as f:
|
|
130
|
+
....: _ = f.write("print('hello world')")
|
|
131
|
+
sage: load(t)
|
|
132
|
+
hello world
|
|
133
|
+
sage: load(t,t)
|
|
134
|
+
hello world
|
|
135
|
+
hello world
|
|
136
|
+
sage: t2 = tmp_filename(); save(2/3,t2)
|
|
137
|
+
sage: load(t,t,t2)
|
|
138
|
+
hello world
|
|
139
|
+
hello world
|
|
140
|
+
[None, None, 2/3]
|
|
141
|
+
|
|
142
|
+
Files with a ``.sage`` extension are preparsed. Also note that we
|
|
143
|
+
can access global variables::
|
|
144
|
+
|
|
145
|
+
sage: t = tmp_filename(ext='.sage')
|
|
146
|
+
sage: with open(t, 'w') as f:
|
|
147
|
+
....: _ = f.write("a += Mod(2/3, 11)") # This evaluates to Mod(8, 11)
|
|
148
|
+
sage: a = -1
|
|
149
|
+
sage: load(t)
|
|
150
|
+
sage: a
|
|
151
|
+
7
|
|
152
|
+
|
|
153
|
+
We can load Fortran files::
|
|
154
|
+
|
|
155
|
+
sage: code = ' subroutine hello\n print *, "Hello World!"\n end subroutine hello\n'
|
|
156
|
+
sage: t = tmp_filename(ext='.F')
|
|
157
|
+
sage: with open(t, 'w') as f:
|
|
158
|
+
....: _ = f.write(code)
|
|
159
|
+
sage: load(t) # needs numpy
|
|
160
|
+
sage: hello # needs numpy
|
|
161
|
+
<fortran ...>
|
|
162
|
+
"""
|
|
163
|
+
import sage.repl.load
|
|
164
|
+
if len(filename) != 1:
|
|
165
|
+
v = [load(file, compress=compress, verbose=verbose, **kwargs) for file in filename]
|
|
166
|
+
# Return v if one of the filenames refers to an object and not
|
|
167
|
+
# a loadable filename.
|
|
168
|
+
for file in filename:
|
|
169
|
+
if not sage.repl.load.is_loadable_filename(file):
|
|
170
|
+
return v
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
filename = filename[0]
|
|
174
|
+
|
|
175
|
+
if sage.repl.load.is_loadable_filename(filename):
|
|
176
|
+
sage.repl.load.load(filename, globals())
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Check if filename starts with "http://" or "https://"
|
|
180
|
+
lower = filename.lower()
|
|
181
|
+
if lower.startswith("http://") or lower.startswith("https://"):
|
|
182
|
+
from sage.misc.remote_file import get_remote_file
|
|
183
|
+
filename = get_remote_file(filename, verbose=verbose)
|
|
184
|
+
tmpfile_flag = True
|
|
185
|
+
else:
|
|
186
|
+
tmpfile_flag = False
|
|
187
|
+
filename = _normalize_filename(filename)
|
|
188
|
+
|
|
189
|
+
# Load file by absolute filename
|
|
190
|
+
with open(filename, 'rb') as fobj:
|
|
191
|
+
X = loads(fobj.read(), compress=compress, **kwargs)
|
|
192
|
+
try:
|
|
193
|
+
X._default_filename = os.path.abspath(filename)
|
|
194
|
+
except AttributeError:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
# Delete the tempfile, if it exists
|
|
198
|
+
if tmpfile_flag:
|
|
199
|
+
os.unlink(filename)
|
|
200
|
+
|
|
201
|
+
return X
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _base_save(obj, filename, compress=True):
|
|
205
|
+
"""
|
|
206
|
+
Base implementation for dumping an object to a ``.sobj`` file.
|
|
207
|
+
|
|
208
|
+
This is the implementation used by :meth:`SageObject.save` and by
|
|
209
|
+
:func:`save` unless the object being saved has a custom ``.save()`` method,
|
|
210
|
+
in which case that is tried first.
|
|
211
|
+
|
|
212
|
+
Otherwise this is equivalent to :func:`_base_dumps` just with the resulting
|
|
213
|
+
pickle data saved to a ``.sobj`` file.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
filename = _normalize_filename(filename)
|
|
217
|
+
|
|
218
|
+
with open(filename, 'wb') as fobj:
|
|
219
|
+
fobj.write(_base_dumps(obj, compress=compress))
|
|
220
|
+
|
|
221
|
+
return filename
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def save(obj, filename, compress=True, **kwargs):
|
|
225
|
+
"""
|
|
226
|
+
Save ``obj`` to the file with name ``filename``, which will have an
|
|
227
|
+
``.sobj`` extension added if it doesn't have one and if ``obj``
|
|
228
|
+
doesn't have its own ``save()`` method, like e.g. Python tuples.
|
|
229
|
+
|
|
230
|
+
For image objects and the like (which have their own ``save()`` method),
|
|
231
|
+
you may have to specify a specific extension, e.g. ``.png``, if you
|
|
232
|
+
don't want the object to be saved as a Sage object (or likewise, if
|
|
233
|
+
``filename`` could be interpreted as already having some extension).
|
|
234
|
+
|
|
235
|
+
.. WARNING::
|
|
236
|
+
|
|
237
|
+
This will *replace* the contents of the file if it already exists.
|
|
238
|
+
|
|
239
|
+
EXAMPLES::
|
|
240
|
+
|
|
241
|
+
sage: import tempfile
|
|
242
|
+
sage: d = tempfile.TemporaryDirectory()
|
|
243
|
+
sage: a = matrix(2, [1,2, 3,-5/2]) # needs sage.modules
|
|
244
|
+
sage: objfile = os.path.join(d.name, 'test.sobj')
|
|
245
|
+
sage: objfile_short = os.path.join(d.name, 'test')
|
|
246
|
+
sage: save(a, objfile) # needs sage.modules
|
|
247
|
+
sage: load(objfile_short) # needs sage.modules
|
|
248
|
+
[ 1 2]
|
|
249
|
+
[ 3 -5/2]
|
|
250
|
+
|
|
251
|
+
sage: # needs sage.plot sage.schemes
|
|
252
|
+
sage: E = EllipticCurve([-1,0])
|
|
253
|
+
sage: P = plot(E)
|
|
254
|
+
sage: save(P, objfile_short) # saves the plot to "test.sobj"
|
|
255
|
+
sage: save(P, filename=os.path.join(d.name, "sage.png"), xmin=-2)
|
|
256
|
+
sage: save(P, os.path.join(d.name, "filename.with.some.wrong.ext"))
|
|
257
|
+
Traceback (most recent call last):
|
|
258
|
+
...
|
|
259
|
+
ValueError: allowed file extensions for images are
|
|
260
|
+
'.eps', '.pdf', '.pgf', '.png', '.ps', '.sobj', '.svg'!
|
|
261
|
+
sage: print(load(objfile))
|
|
262
|
+
Graphics object consisting of 2 graphics primitives
|
|
263
|
+
|
|
264
|
+
sage: save("A python string", os.path.join(d.name, 'test'))
|
|
265
|
+
sage: load(objfile)
|
|
266
|
+
'A python string'
|
|
267
|
+
sage: load(objfile_short)
|
|
268
|
+
'A python string'
|
|
269
|
+
sage: d.cleanup()
|
|
270
|
+
|
|
271
|
+
TESTS:
|
|
272
|
+
|
|
273
|
+
Check that :issue:`11577` is fixed::
|
|
274
|
+
|
|
275
|
+
sage: import tempfile
|
|
276
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.bar') as f:
|
|
277
|
+
....: save((1,1), f.name)
|
|
278
|
+
....: load(f.name)
|
|
279
|
+
(1, 1)
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
if not os.path.splitext(filename)[1] or not hasattr(obj, 'save'):
|
|
283
|
+
filename = _normalize_filename(filename)
|
|
284
|
+
|
|
285
|
+
if filename.endswith('.sobj'):
|
|
286
|
+
try:
|
|
287
|
+
obj.save(filename=filename, compress=compress, **kwargs)
|
|
288
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
289
|
+
_base_save(obj, filename, compress=compress)
|
|
290
|
+
else:
|
|
291
|
+
# Saving an object to an image file.
|
|
292
|
+
obj.save(filename, **kwargs)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _base_dumps(obj, compress=True):
|
|
296
|
+
"""
|
|
297
|
+
Base implementation for dumping an object to a pickle in Sage.
|
|
298
|
+
|
|
299
|
+
This is the implementation used by :meth:`SageObject.dumps` and by
|
|
300
|
+
:func:`dumps` unless the object being dumped has a custom ``.dumps()``
|
|
301
|
+
method, in which case that is tried first.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
global already_pickled
|
|
305
|
+
gherkin = SagePickler.dumps(obj)
|
|
306
|
+
already_pickled = {}
|
|
307
|
+
|
|
308
|
+
if compress:
|
|
309
|
+
return comp.compress(gherkin)
|
|
310
|
+
|
|
311
|
+
return gherkin
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def dumps(obj, compress=True):
|
|
315
|
+
"""
|
|
316
|
+
Dump obj to a string s. To recover obj, use ``loads(s)``.
|
|
317
|
+
|
|
318
|
+
.. SEEALSO:: :func:`loads`
|
|
319
|
+
|
|
320
|
+
EXAMPLES::
|
|
321
|
+
|
|
322
|
+
sage: a = 2/3
|
|
323
|
+
sage: s = dumps(a)
|
|
324
|
+
sage: a2 = loads(s)
|
|
325
|
+
sage: type(a) is type(a2)
|
|
326
|
+
True
|
|
327
|
+
sage: a2
|
|
328
|
+
2/3
|
|
329
|
+
"""
|
|
330
|
+
global already_pickled
|
|
331
|
+
if make_pickle_jar:
|
|
332
|
+
picklejar(obj)
|
|
333
|
+
try:
|
|
334
|
+
ans = obj.dumps(compress)
|
|
335
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
336
|
+
ans = _base_dumps(obj, compress=compress)
|
|
337
|
+
already_pickled = {}
|
|
338
|
+
return ans
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# This is used below, and also by explain_pickle.py
|
|
342
|
+
unpickle_override = {}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def register_unpickle_override(module, name, callable, call_name=None):
|
|
346
|
+
r"""
|
|
347
|
+
Python pickles include the module and class name of classes.
|
|
348
|
+
This means that rearranging the Sage source can invalidate old
|
|
349
|
+
pickles. To keep the old pickles working, you can call
|
|
350
|
+
register_unpickle_override with an old module name and class name,
|
|
351
|
+
and the Python callable (function, class with __call__ method, etc.)
|
|
352
|
+
to use for unpickling. (If this callable is a value in some module,
|
|
353
|
+
you can specify the module name and class name, for the benefit of
|
|
354
|
+
:func:`~sage.misc.explain_pickle.explain_pickle` when called with ``in_current_sage=True``).)
|
|
355
|
+
|
|
356
|
+
EXAMPLES:
|
|
357
|
+
|
|
358
|
+
Imagine that there used to be an ``old_integer`` module and old
|
|
359
|
+
pickles essentially trying to do the following::
|
|
360
|
+
|
|
361
|
+
sage: unpickle_global('sage.rings.old_integer', 'OldInteger')
|
|
362
|
+
Traceback (most recent call last):
|
|
363
|
+
...
|
|
364
|
+
ImportError: cannot import OldInteger from sage.rings.old_integer, call register_unpickle_override('sage.rings.old_integer', 'OldInteger', ...) to fix this
|
|
365
|
+
|
|
366
|
+
After following the advice from the error message, unpickling
|
|
367
|
+
works::
|
|
368
|
+
|
|
369
|
+
sage: from sage.misc.persist import register_unpickle_override
|
|
370
|
+
sage: register_unpickle_override('sage.rings.old_integer', 'OldInteger', Integer)
|
|
371
|
+
sage: unpickle_global('sage.rings.old_integer', 'OldInteger')
|
|
372
|
+
<... 'sage.rings.integer.Integer'>
|
|
373
|
+
|
|
374
|
+
In many cases, unpickling problems for old pickles can be resolved with a
|
|
375
|
+
simple call to ``register_unpickle_override``, as in the example above and
|
|
376
|
+
in many of the ``sage`` source files. However, if the underlying data
|
|
377
|
+
structure has changed significantly then unpickling may fail and it
|
|
378
|
+
will be necessary to explicitly implement unpickling methods for the
|
|
379
|
+
associated objects. The python pickle protocol is described in detail on the
|
|
380
|
+
web and, in particular, in the `python pickling documentation`_. For example, the
|
|
381
|
+
following excerpt from this documentation shows that the unpickling of
|
|
382
|
+
classes is controlled by their :meth:`__setstate__` method.
|
|
383
|
+
|
|
384
|
+
::
|
|
385
|
+
|
|
386
|
+
object.__setstate__(state)
|
|
387
|
+
|
|
388
|
+
Upon unpickling, if the class also defines the method :meth:`__setstate__`, it is
|
|
389
|
+
called with the unpickled state. If there is no :meth:`__setstate__` method,
|
|
390
|
+
the pickled state must be a dictionary and its items are assigned to the new
|
|
391
|
+
instance's dictionary. If a class defines both :meth:`__getstate__` and
|
|
392
|
+
:meth:`__setstate__`, the state object needn't be a dictionary and these methods
|
|
393
|
+
can do what they want.
|
|
394
|
+
|
|
395
|
+
.. _python pickling documentation: https://docs.python.org/library/pickle.html#pickle-protocol
|
|
396
|
+
|
|
397
|
+
By implementing a :meth:`__setstate__` method for a class it should be
|
|
398
|
+
possible to fix any unpickling problems for the class. As an example of what
|
|
399
|
+
needs to be done, we show how to unpickle a :class:`CombinatorialObject`
|
|
400
|
+
object using a class which also inherits from
|
|
401
|
+
:class:`~sage.structure.element.Element`. This exact problem often arises
|
|
402
|
+
when refactoring old code into the element framework. First we create a
|
|
403
|
+
pickle to play with::
|
|
404
|
+
|
|
405
|
+
sage: from sage.structure.element import Element
|
|
406
|
+
sage: class SourPickle(CombinatorialObject): pass
|
|
407
|
+
sage: class SweetPickle(CombinatorialObject, Element): pass
|
|
408
|
+
sage: import __main__
|
|
409
|
+
sage: __main__.SourPickle = SourPickle
|
|
410
|
+
sage: __main__.SweetPickle = SweetPickle # a hack to allow us to pickle command line classes
|
|
411
|
+
sage: gherkin = dumps(SourPickle([1, 2, 3]))
|
|
412
|
+
|
|
413
|
+
Using :func:`register_unpickle_override` we try to sweeten our pickle, but
|
|
414
|
+
we are unable to eat it::
|
|
415
|
+
|
|
416
|
+
sage: from sage.misc.persist import register_unpickle_override
|
|
417
|
+
sage: register_unpickle_override('__main__', 'SourPickle', SweetPickle)
|
|
418
|
+
sage: loads(gherkin)
|
|
419
|
+
Traceback (most recent call last):
|
|
420
|
+
...
|
|
421
|
+
KeyError: 0
|
|
422
|
+
|
|
423
|
+
The problem is that the ``SweetPickle`` has inherited a
|
|
424
|
+
:meth:`~sage.structure.element.Element.__setstate__` method from
|
|
425
|
+
:class:`~sage.structure.element.Element` which is not compatible with
|
|
426
|
+
unpickling for :class:`CombinatorialObject`. We can fix this by explicitly
|
|
427
|
+
defining a new :meth:`__setstate__` method::
|
|
428
|
+
|
|
429
|
+
sage: class SweeterPickle(CombinatorialObject, Element):
|
|
430
|
+
....: def __setstate__(self, state):
|
|
431
|
+
....: # a pickle from CombinatorialObject is just its instance
|
|
432
|
+
....: # dictionary
|
|
433
|
+
....: if isinstance(state, dict):
|
|
434
|
+
....: # this is a fudge: we need an appropriate parent here
|
|
435
|
+
....: self._set_parent(Tableaux())
|
|
436
|
+
....: self.__dict__ = state
|
|
437
|
+
....: else:
|
|
438
|
+
....: P, D = state
|
|
439
|
+
....: if P is not None:
|
|
440
|
+
....: self._set_parent(P)
|
|
441
|
+
....: self.__dict__ = D
|
|
442
|
+
sage: __main__.SweeterPickle = SweeterPickle
|
|
443
|
+
sage: register_unpickle_override('__main__', 'SourPickle', SweeterPickle)
|
|
444
|
+
sage: loads(gherkin) # needs sage.combinat
|
|
445
|
+
[1, 2, 3]
|
|
446
|
+
sage: loads(dumps(SweeterPickle([1, 2, 3]))) # check that pickles work for SweeterPickle
|
|
447
|
+
[1, 2, 3]
|
|
448
|
+
|
|
449
|
+
The ``state`` passed to :meth:`__setstate__` will usually be something like
|
|
450
|
+
the instance dictionary of the pickled object, however, with some older
|
|
451
|
+
classes such as :class:`CombinatorialObject` it will be a tuple. In general,
|
|
452
|
+
the ``state`` can be any python object. ``Sage`` provides a special tool,
|
|
453
|
+
:func:`~sage.misc.explain_pickle.explain_pickle`, which can help in figuring
|
|
454
|
+
out the contents of an old pickle. Here is a second example.
|
|
455
|
+
|
|
456
|
+
::
|
|
457
|
+
|
|
458
|
+
sage: class A():
|
|
459
|
+
....: def __init__(self, value):
|
|
460
|
+
....: self.original_attribute = value
|
|
461
|
+
....: def __repr__(self):
|
|
462
|
+
....: return 'A(%s)' % self.original_attribute
|
|
463
|
+
sage: class B():
|
|
464
|
+
....: def __init__(self, value):
|
|
465
|
+
....: self.new_attribute = value
|
|
466
|
+
....: def __setstate__(self, state):
|
|
467
|
+
....: try:
|
|
468
|
+
....: self.new_attribute = state['new_attribute']
|
|
469
|
+
....: except KeyError: # an old pickle
|
|
470
|
+
....: self.new_attribute = state['original_attribute']
|
|
471
|
+
....: def __repr__(self):
|
|
472
|
+
....: return 'B(%s)' % self.new_attribute
|
|
473
|
+
sage: import __main__
|
|
474
|
+
sage: # a hack to allow us to pickle command line classes
|
|
475
|
+
sage: __main__.A = A
|
|
476
|
+
sage: __main__.B = B
|
|
477
|
+
sage: A(10)
|
|
478
|
+
A(10)
|
|
479
|
+
sage: loads(dumps(A(10)))
|
|
480
|
+
A(10)
|
|
481
|
+
sage: sage.misc.explain_pickle.explain_pickle(dumps(A(10)))
|
|
482
|
+
pg_A = unpickle_global('__main__', 'A')
|
|
483
|
+
si = unpickle_newobj(pg_A, ())
|
|
484
|
+
pg_make_integer = unpickle_global('sage.rings.integer', 'make_integer')
|
|
485
|
+
unpickle_build(si, {'original_attribute':pg_make_integer('a')})
|
|
486
|
+
si
|
|
487
|
+
sage: from sage.misc.persist import register_unpickle_override
|
|
488
|
+
sage: register_unpickle_override('__main__', 'A', B)
|
|
489
|
+
sage: loads(dumps(A(10)))
|
|
490
|
+
B(10)
|
|
491
|
+
sage: loads(dumps(B(10)))
|
|
492
|
+
B(10)
|
|
493
|
+
|
|
494
|
+
Pickling for python classes and extension classes, such as cython, is
|
|
495
|
+
different -- again this is discussed in the `python pickling
|
|
496
|
+
documentation`_. For the unpickling of extension classes you need to write
|
|
497
|
+
a :meth:`__reduce__` method which typically returns a tuple ``(f,
|
|
498
|
+
args,...)`` such that ``f(*args)`` returns (a copy of) the original object.
|
|
499
|
+
The following code snippet is the
|
|
500
|
+
:meth:`~sage.rings.integer.Integer.__reduce__` method from
|
|
501
|
+
:class:`sage.rings.integer.Integer`.
|
|
502
|
+
|
|
503
|
+
.. code-block:: cython
|
|
504
|
+
|
|
505
|
+
def __reduce__(self):
|
|
506
|
+
'Including the documentation properly causes a doc-test failure so we include it as a comment:'
|
|
507
|
+
#* '''
|
|
508
|
+
#* This is used when pickling integers.
|
|
509
|
+
#*
|
|
510
|
+
#* EXAMPLES::
|
|
511
|
+
#*
|
|
512
|
+
#* sage: n = 5
|
|
513
|
+
#* sage: t = n.__reduce__(); t
|
|
514
|
+
#* (<built-in function make_integer>, ('5',))
|
|
515
|
+
#* sage: t[0](*t[1])
|
|
516
|
+
#* 5
|
|
517
|
+
#* sage: loads(dumps(n)) == n
|
|
518
|
+
#* True
|
|
519
|
+
#* '''
|
|
520
|
+
# This single line below took me HOURS to figure out.
|
|
521
|
+
# It is the *trick* needed to pickle Cython extension types.
|
|
522
|
+
# The trick is that you must put a pure Python function
|
|
523
|
+
# as the first argument, and that function must return
|
|
524
|
+
# the result of unpickling with the argument in the second
|
|
525
|
+
# tuple as input. All kinds of problems happen
|
|
526
|
+
# if we don't do this.
|
|
527
|
+
return sage.rings.integer.make_integer, (self.str(32),)
|
|
528
|
+
"""
|
|
529
|
+
unpickle_override[(module, name)] = (callable, call_name)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def unpickle_global(module, name):
|
|
533
|
+
r"""
|
|
534
|
+
Given a module name and a name within that module (typically a class
|
|
535
|
+
name), retrieve the corresponding object. This normally just looks
|
|
536
|
+
up the name in the module, but it can be overridden by
|
|
537
|
+
register_unpickle_override. This is used in the Sage unpickling
|
|
538
|
+
mechanism, so if the Sage source code organization changes,
|
|
539
|
+
register_unpickle_override can allow old pickles to continue to work.
|
|
540
|
+
|
|
541
|
+
EXAMPLES::
|
|
542
|
+
|
|
543
|
+
sage: from sage.misc.persist import unpickle_override, register_unpickle_override
|
|
544
|
+
sage: unpickle_global('sage.rings.integer', 'Integer')
|
|
545
|
+
<class 'sage.rings.integer.Integer'>
|
|
546
|
+
|
|
547
|
+
Now we horribly break the pickling system::
|
|
548
|
+
|
|
549
|
+
sage: register_unpickle_override('sage.rings.integer', 'Integer', Rational, call_name=('sage.rings.rational', 'Rational'))
|
|
550
|
+
sage: unpickle_global('sage.rings.integer', 'Integer')
|
|
551
|
+
<class 'sage.rings.rational.Rational'>
|
|
552
|
+
|
|
553
|
+
and we reach into the internals and put it back::
|
|
554
|
+
|
|
555
|
+
sage: del unpickle_override[('sage.rings.integer', 'Integer')]
|
|
556
|
+
sage: unpickle_global('sage.rings.integer', 'Integer')
|
|
557
|
+
<class 'sage.rings.integer.Integer'>
|
|
558
|
+
|
|
559
|
+
A meaningful error message with resolution instructions is displayed for
|
|
560
|
+
old pickles that accidentally got broken because a class or entire module
|
|
561
|
+
was moved or renamed::
|
|
562
|
+
|
|
563
|
+
sage: unpickle_global('sage.all', 'some_old_class')
|
|
564
|
+
Traceback (most recent call last):
|
|
565
|
+
...
|
|
566
|
+
ImportError: cannot import some_old_class from sage.all, call
|
|
567
|
+
register_unpickle_override('sage.all', 'some_old_class', ...)
|
|
568
|
+
to fix this
|
|
569
|
+
|
|
570
|
+
sage: unpickle_global('sage.some_old_module', 'some_old_class')
|
|
571
|
+
Traceback (most recent call last):
|
|
572
|
+
...
|
|
573
|
+
ImportError: cannot import some_old_class from sage.some_old_module, call
|
|
574
|
+
register_unpickle_override('sage.some_old_module', 'some_old_class', ...)
|
|
575
|
+
to fix this
|
|
576
|
+
|
|
577
|
+
TESTS:
|
|
578
|
+
|
|
579
|
+
Test that :func:`register_unpickle_override` calls in lazily imported modules
|
|
580
|
+
are respected::
|
|
581
|
+
|
|
582
|
+
sage: unpickle_global('sage.combinat.root_system.type_A', 'ambient_space') # needs sage.modules
|
|
583
|
+
<class 'sage.combinat.root_system.type_A.AmbientSpace'>
|
|
584
|
+
"""
|
|
585
|
+
unpickler = unpickle_override.get((module, name))
|
|
586
|
+
if unpickler is not None:
|
|
587
|
+
return unpickler[0]
|
|
588
|
+
|
|
589
|
+
def error():
|
|
590
|
+
raise ImportError("cannot import {1} from {0}, call "
|
|
591
|
+
"register_unpickle_override({0!r}, {1!r}, ...) to fix this".format(module, name))
|
|
592
|
+
|
|
593
|
+
mod = sys.modules.get(module)
|
|
594
|
+
if mod is not None:
|
|
595
|
+
try:
|
|
596
|
+
return getattr(mod, name)
|
|
597
|
+
except AttributeError:
|
|
598
|
+
error()
|
|
599
|
+
try:
|
|
600
|
+
__import__(module)
|
|
601
|
+
except ImportError:
|
|
602
|
+
error()
|
|
603
|
+
|
|
604
|
+
# Importing the module may have run register_unpickle_override.
|
|
605
|
+
unpickler = unpickle_override.get((module, name))
|
|
606
|
+
if unpickler is not None:
|
|
607
|
+
return unpickler[0]
|
|
608
|
+
|
|
609
|
+
mod = sys.modules[module]
|
|
610
|
+
return getattr(mod, name)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class _BasePickler(pickle.Pickler):
|
|
614
|
+
"""
|
|
615
|
+
Provides the Python 3 implementation for
|
|
616
|
+
:class:`sage.misc.persist.SagePickler`.
|
|
617
|
+
|
|
618
|
+
This is simpler than the Python 2 case since ``pickle.Pickler`` is a
|
|
619
|
+
modern built-in type which can be easily subclassed to provide new
|
|
620
|
+
functionality.
|
|
621
|
+
|
|
622
|
+
See the documentation for that class for tests and examples.
|
|
623
|
+
"""
|
|
624
|
+
|
|
625
|
+
def __init__(self, file_obj, protocol=None, persistent_id=None, *,
|
|
626
|
+
fix_imports=True):
|
|
627
|
+
super(_BasePickler, self).__init__(file_obj, protocol,
|
|
628
|
+
fix_imports=fix_imports)
|
|
629
|
+
self._persistent_id = persistent_id
|
|
630
|
+
|
|
631
|
+
def persistent_id(self, obj):
|
|
632
|
+
"""
|
|
633
|
+
Implement persistence of external objects with the
|
|
634
|
+
``persistent_id`` function given at instantiation, if any.
|
|
635
|
+
Otherwise returns ``None`` as in the base class.
|
|
636
|
+
|
|
637
|
+
See the documentation for
|
|
638
|
+
:class:`sage.misc.persist.SagePickler` for more details.
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
if self._persistent_id is not None:
|
|
642
|
+
return self._persistent_id(obj)
|
|
643
|
+
|
|
644
|
+
return super(_BasePickler, self).persistent_id(obj)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
# Since Python 3.4, protocol version 4 is the "best" protocol, so we
|
|
648
|
+
# use that by default on Sage
|
|
649
|
+
_DEFAULT_PROTOCOL_VERSION = 4
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class _BaseUnpickler(pickle.Unpickler):
|
|
653
|
+
"""
|
|
654
|
+
Provides the Python 3 implementation for
|
|
655
|
+
:class:`sage.misc.persist.SageUnpickler`.
|
|
656
|
+
|
|
657
|
+
This is simpler than the Python 2 case since ``pickle.Unpickler`` is
|
|
658
|
+
a modern built-in type which can be easily subclassed to provide new
|
|
659
|
+
functionality.
|
|
660
|
+
|
|
661
|
+
See the documentation for that class for tests and examples.
|
|
662
|
+
"""
|
|
663
|
+
|
|
664
|
+
def __init__(self, file_obj, persistent_load=None, *, **kwargs):
|
|
665
|
+
kwargs.setdefault('encoding', 'latin1')
|
|
666
|
+
super(_BaseUnpickler, self).__init__(file_obj, **kwargs)
|
|
667
|
+
self._persistent_load = persistent_load
|
|
668
|
+
|
|
669
|
+
def persistent_load(self, pid):
|
|
670
|
+
"""
|
|
671
|
+
Implement persistent loading with the ``persistent_load`` function
|
|
672
|
+
given at instantiation, if any. Otherwise raises a
|
|
673
|
+
``pickle.UnpicklingError`` as in the base class.
|
|
674
|
+
|
|
675
|
+
See the documentation for :class:`sage.misc.persist.SageUnpickler`
|
|
676
|
+
for more details.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
if self._persistent_load is not None:
|
|
680
|
+
return self._persistent_load(pid)
|
|
681
|
+
|
|
682
|
+
return super(_BaseUnpickler, self).persistent_load(pid)
|
|
683
|
+
|
|
684
|
+
def find_class(self, module, name):
|
|
685
|
+
"""
|
|
686
|
+
The Unpickler uses this class to load module-level objects.
|
|
687
|
+
Contrary to the name, it is used for functions as well as classes.
|
|
688
|
+
|
|
689
|
+
(This is equivalent to what was previously called ``find_global``
|
|
690
|
+
which seems like a better name, albeit still somewhat misleading).
|
|
691
|
+
|
|
692
|
+
This is just a thin wrapper around
|
|
693
|
+
:func:`sage.misc.persist.unpickle_global`
|
|
694
|
+
"""
|
|
695
|
+
|
|
696
|
+
# First try using Sage's unpickle_global to go through the unpickle
|
|
697
|
+
# override system
|
|
698
|
+
try:
|
|
699
|
+
return unpickle_global(module, name)
|
|
700
|
+
except ImportError:
|
|
701
|
+
# Failing that, go through the base class's find_class to give
|
|
702
|
+
# it a try (this is necessary in particular to support
|
|
703
|
+
# fix_imports)
|
|
704
|
+
return super(_BaseUnpickler, self).find_class(module, name)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
class SagePickler(_BasePickler):
|
|
708
|
+
r"""
|
|
709
|
+
Subclass ``pickle.Pickler`` with Sage-specific default options, and
|
|
710
|
+
built-in support for external object persistence.
|
|
711
|
+
|
|
712
|
+
INPUT:
|
|
713
|
+
|
|
714
|
+
- ``file_obj`` -- a readable file-like object returning ``bytes`` from
|
|
715
|
+
which the pickle data will be loaded
|
|
716
|
+
|
|
717
|
+
- ``persistent_id`` -- callable or None; if given this callable takes a
|
|
718
|
+
single object to be pickled, and returns an "ID" (a key with which to
|
|
719
|
+
restore the object upon unpickling, which may itself be any pickleable
|
|
720
|
+
object). See the Python documentation on `pickling and unpickling
|
|
721
|
+
external objects`_ for more details.
|
|
722
|
+
|
|
723
|
+
- ``py2compat`` -- on Python 3 only, this creates pickles that have a
|
|
724
|
+
better chance of being read on Python 2, by using protocol version 2
|
|
725
|
+
(instead of 4) and fixing up imports of standard library modules and
|
|
726
|
+
types whose names changed between Python 2 and 3. This is enabled by
|
|
727
|
+
default for the best chances of cross-Python compatibility.
|
|
728
|
+
|
|
729
|
+
- Further arguments are passed to :func:`pickle.load`, where in Python-3
|
|
730
|
+
Sage sets the default ``encoding='latin1'``. This is essential to make
|
|
731
|
+
pickles readable in Python-3 that were created in Python-2. See
|
|
732
|
+
:issue:`28444` for details.
|
|
733
|
+
|
|
734
|
+
.. _pickling and unpickling external objects: https://docs.python.org/2.7/library/pickle.html#pickling-and-unpickling-external-objects
|
|
735
|
+
|
|
736
|
+
EXAMPLES::
|
|
737
|
+
|
|
738
|
+
sage: from sage.misc.persist import (
|
|
739
|
+
....: unpickle_override, register_unpickle_override, SageUnpickler)
|
|
740
|
+
sage: from sage.rings.integer import make_integer
|
|
741
|
+
sage: from io import BytesIO
|
|
742
|
+
sage: def fake_constructor(x):
|
|
743
|
+
....: print("unpickling an Integer")
|
|
744
|
+
....: return make_integer(x)
|
|
745
|
+
sage: register_unpickle_override('sage.rings.integer', 'make_integer',
|
|
746
|
+
....: fake_constructor)
|
|
747
|
+
sage: unp = SageUnpickler(BytesIO(dumps(1, compress=False)))
|
|
748
|
+
sage: unp.load()
|
|
749
|
+
unpickling an Integer
|
|
750
|
+
1
|
|
751
|
+
sage: del unpickle_override[('sage.rings.integer', 'make_integer')]
|
|
752
|
+
|
|
753
|
+
The ``SagePickler`` can also be passed a ``persistent_id`` function::
|
|
754
|
+
|
|
755
|
+
sage: table = {1: 'a', 2: 'b'}
|
|
756
|
+
sage: # in practice this might be a database or something...
|
|
757
|
+
sage: def load_object_from_table(obj_id):
|
|
758
|
+
....: tag, obj_id
|
|
759
|
+
....: return table[obj_id]
|
|
760
|
+
|
|
761
|
+
TESTS:
|
|
762
|
+
|
|
763
|
+
The following is an indirect doctest.
|
|
764
|
+
::
|
|
765
|
+
|
|
766
|
+
sage: class Foo():
|
|
767
|
+
....: def __init__(self, s):
|
|
768
|
+
....: self.bar = s
|
|
769
|
+
....: def __reduce__(self):
|
|
770
|
+
....: return Foo, (self.bar,)
|
|
771
|
+
....:
|
|
772
|
+
sage: import __main__
|
|
773
|
+
sage: __main__.Foo = Foo
|
|
774
|
+
|
|
775
|
+
The data that is passed to ``loads`` in the following line was created
|
|
776
|
+
by ``dumps(Foo('\x80\x07')`` in Python-2. We demonstrate that it can
|
|
777
|
+
be correctly unpickled in Python-3::
|
|
778
|
+
|
|
779
|
+
sage: g = loads(b'x\x9ck`J\x8e\x8f\xcfM\xcc\xcc\x8b\x8f\xe7r\xcb\xcf\xe7*d\x0cej`/dj\r*d\xd6\x03\x00\x89\xc5\x08{')
|
|
780
|
+
sage: type(g), g.bar
|
|
781
|
+
(<class '__main__.Foo'>, '\x80\x07')
|
|
782
|
+
|
|
783
|
+
The following line demonstrates what would happen without :issue:`28444`::
|
|
784
|
+
|
|
785
|
+
sage: loads(b'x\x9ck`J\x8e\x8f\xcfM\xcc\xcc\x8b\x8f\xe7r\xcb\xcf\xe7*d\x0cej`/dj\r*d\xd6\x03\x00\x89\xc5\x08{', encoding='ASCII')
|
|
786
|
+
Traceback (most recent call last):
|
|
787
|
+
...
|
|
788
|
+
UnicodeDecodeError: 'ascii' codec can...t decode byte 0x80 in position 0: ordinal not in range(128)
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
def __init__(self, file_obj, persistent_id=None, py2compat=True):
|
|
792
|
+
protocol = _DEFAULT_PROTOCOL_VERSION
|
|
793
|
+
|
|
794
|
+
if py2compat:
|
|
795
|
+
protocol = 2
|
|
796
|
+
|
|
797
|
+
super(SagePickler, self).__init__(file_obj, protocol=protocol,
|
|
798
|
+
persistent_id=persistent_id)
|
|
799
|
+
|
|
800
|
+
@classmethod
|
|
801
|
+
def dumps(cls, obj, **kwargs):
|
|
802
|
+
"""
|
|
803
|
+
Equivalent to :func:`pickle.dumps` but using the
|
|
804
|
+
:class:`sage.misc.persist.SagePickler`.
|
|
805
|
+
|
|
806
|
+
INPUT:
|
|
807
|
+
|
|
808
|
+
- ``obj`` -- the object to pickle
|
|
809
|
+
|
|
810
|
+
- ``kwargs`` -- keyword arguments passed to the
|
|
811
|
+
:class:`sage.misc.persist.SagePickler` constructor
|
|
812
|
+
|
|
813
|
+
OUTPUT: ``pickle`` -- the pickled object as ``bytes``
|
|
814
|
+
|
|
815
|
+
EXAMPLES::
|
|
816
|
+
|
|
817
|
+
sage: import pickle
|
|
818
|
+
sage: from sage.misc.persist import SagePickler
|
|
819
|
+
sage: gherkin = SagePickler.dumps(1)
|
|
820
|
+
sage: pickle.loads(gherkin)
|
|
821
|
+
1
|
|
822
|
+
"""
|
|
823
|
+
global already_pickled
|
|
824
|
+
buf = io.BytesIO()
|
|
825
|
+
pickler = cls(buf, **kwargs)
|
|
826
|
+
pickler.dump(obj)
|
|
827
|
+
already_pickled = {}
|
|
828
|
+
return buf.getvalue()
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
class SageUnpickler(_BaseUnpickler):
|
|
832
|
+
"""
|
|
833
|
+
Subclass ``pickle.Unpickler`` to control how certain objects get unpickled
|
|
834
|
+
(registered overrides, specifically).
|
|
835
|
+
|
|
836
|
+
This is only needed in Python 3 and up. On Python 2 the behavior of the
|
|
837
|
+
``cPickle`` module is customized differently.
|
|
838
|
+
|
|
839
|
+
This class simply overrides ``Unpickler.find_class`` to wrap
|
|
840
|
+
``sage.misc.persist.unpickle_global``.
|
|
841
|
+
|
|
842
|
+
INPUT:
|
|
843
|
+
|
|
844
|
+
- ``file_obj`` -- a readable file-like object returning ``bytes`` from
|
|
845
|
+
which the pickle data will be loaded
|
|
846
|
+
|
|
847
|
+
- ``persistent_load`` -- callable or None; if given this callable
|
|
848
|
+
implements loading of persistent external objects. The function
|
|
849
|
+
should take a single argument, the persistent object ID. See the
|
|
850
|
+
Python documentation on `pickling and unpickling external objects`_
|
|
851
|
+
for more details.
|
|
852
|
+
|
|
853
|
+
- ``kwargs`` -- additional keyword arguments passed to the
|
|
854
|
+
``pickle.Unpickler`` constructor
|
|
855
|
+
|
|
856
|
+
.. _pickling and unpickling external objects: https://docs.python.org/2.7/library/pickle.html#pickling-and-unpickling-external-objects
|
|
857
|
+
|
|
858
|
+
EXAMPLES::
|
|
859
|
+
|
|
860
|
+
sage: from sage.misc.persist import (
|
|
861
|
+
....: unpickle_override, register_unpickle_override, SageUnpickler)
|
|
862
|
+
sage: from sage.rings.integer import make_integer
|
|
863
|
+
sage: from io import BytesIO
|
|
864
|
+
sage: def fake_constructor(x):
|
|
865
|
+
....: print("unpickling an Integer")
|
|
866
|
+
....: return make_integer(x)
|
|
867
|
+
sage: register_unpickle_override('sage.rings.integer', 'make_integer',
|
|
868
|
+
....: fake_constructor)
|
|
869
|
+
sage: unp = SageUnpickler(BytesIO(dumps(1, compress=False)))
|
|
870
|
+
sage: unp.load()
|
|
871
|
+
unpickling an Integer
|
|
872
|
+
1
|
|
873
|
+
sage: del unpickle_override[('sage.rings.integer', 'make_integer')]
|
|
874
|
+
|
|
875
|
+
The ``SageUnpickler`` can also be passed a ``persistent_load`` function::
|
|
876
|
+
|
|
877
|
+
sage: table = {1: 'a', 2: 'b'}
|
|
878
|
+
sage: # in practice this might be a database or something...
|
|
879
|
+
sage: def load_object_from_table(obj_id):
|
|
880
|
+
....: tag, obj_id
|
|
881
|
+
....: return table[obj_id]
|
|
882
|
+
"""
|
|
883
|
+
|
|
884
|
+
@classmethod
|
|
885
|
+
def loads(cls, data, **kwargs):
|
|
886
|
+
"""
|
|
887
|
+
Equivalent to :func:`pickle.loads` but using the
|
|
888
|
+
:class:`sage.misc.persist.SagePickler`.
|
|
889
|
+
|
|
890
|
+
INPUT:
|
|
891
|
+
|
|
892
|
+
- ``data`` -- the pickle data as ``bytes``
|
|
893
|
+
|
|
894
|
+
- ``kwargs`` -- keyword arguments passed to the
|
|
895
|
+
:class:`sage.misc.persist.SageUnpickler` constructor
|
|
896
|
+
|
|
897
|
+
OUTPUT: ``obj`` -- the object that was serialized to the given pickle data
|
|
898
|
+
|
|
899
|
+
EXAMPLES::
|
|
900
|
+
|
|
901
|
+
sage: import pickle
|
|
902
|
+
sage: from sage.misc.persist import SageUnpickler
|
|
903
|
+
sage: gherkin = pickle.dumps(1)
|
|
904
|
+
sage: SageUnpickler.loads(gherkin)
|
|
905
|
+
1
|
|
906
|
+
"""
|
|
907
|
+
|
|
908
|
+
return cls(io.BytesIO(data), **kwargs).load()
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def loads(s, compress=True, **kwargs):
|
|
912
|
+
r"""
|
|
913
|
+
Recover an object x that has been dumped to a string s
|
|
914
|
+
using ``s = dumps(x)``.
|
|
915
|
+
|
|
916
|
+
.. SEEALSO:: :func:`dumps`
|
|
917
|
+
|
|
918
|
+
EXAMPLES::
|
|
919
|
+
|
|
920
|
+
sage: a = matrix(2, [1,2, 3,-4/3]) # needs sage.modules
|
|
921
|
+
sage: s = dumps(a) # needs sage.modules
|
|
922
|
+
sage: loads(s) # needs sage.modules
|
|
923
|
+
[ 1 2]
|
|
924
|
+
[ 3 -4/3]
|
|
925
|
+
|
|
926
|
+
If compress is ``True`` (the default), it will try to decompress
|
|
927
|
+
the data with zlib and with bz2 (in turn); if neither succeeds,
|
|
928
|
+
it will assume the data is actually uncompressed. If ``compress==False``
|
|
929
|
+
is explicitly specified, then no decompression is attempted.
|
|
930
|
+
Further arguments are passed to python's :func:`pickle.load`.
|
|
931
|
+
|
|
932
|
+
::
|
|
933
|
+
|
|
934
|
+
sage: v = [1..10]
|
|
935
|
+
sage: loads(dumps(v, compress=False)) == v
|
|
936
|
+
True
|
|
937
|
+
sage: loads(dumps(v, compress=False), compress=True) == v
|
|
938
|
+
True
|
|
939
|
+
sage: loads(dumps(v, compress=True), compress=False)
|
|
940
|
+
Traceback (most recent call last):
|
|
941
|
+
...
|
|
942
|
+
UnpicklingError: invalid load key, 'x'.
|
|
943
|
+
|
|
944
|
+
The next example demonstrates that Sage strives to avoid data loss
|
|
945
|
+
in the transition from Python-2 to Python-3. The problem is that Python-3
|
|
946
|
+
by default would not be able to unpickle a non-ASCII Python-2 string appearing
|
|
947
|
+
in a pickle. See :issue:`28444` for details.
|
|
948
|
+
::
|
|
949
|
+
|
|
950
|
+
sage: class Foo():
|
|
951
|
+
....: def __init__(self, s):
|
|
952
|
+
....: self.bar = s
|
|
953
|
+
....: def __reduce__(self):
|
|
954
|
+
....: return Foo, (self.bar,)
|
|
955
|
+
....:
|
|
956
|
+
sage: import __main__
|
|
957
|
+
sage: __main__.Foo = Foo
|
|
958
|
+
|
|
959
|
+
The data that is passed to ``loads`` in the following line was created
|
|
960
|
+
by ``dumps(Foo('\x80\x07')`` in Python-2.
|
|
961
|
+
::
|
|
962
|
+
|
|
963
|
+
sage: g = loads(b'x\x9ck`J\x8e\x8f\xcfM\xcc\xcc\x8b\x8f\xe7r\xcb\xcf\xe7*d\x0cej`/dj\r*d\xd6\x03\x00\x89\xc5\x08{')
|
|
964
|
+
sage: type(g), g.bar
|
|
965
|
+
(<class '__main__.Foo'>, '\x80\x07')
|
|
966
|
+
|
|
967
|
+
The following line demonstrates what would happen without :issue:`28444`::
|
|
968
|
+
|
|
969
|
+
sage: loads(b'x\x9ck`J\x8e\x8f\xcfM\xcc\xcc\x8b\x8f\xe7r\xcb\xcf\xe7*d\x0cej`/dj\r*d\xd6\x03\x00\x89\xc5\x08{', encoding='ASCII')
|
|
970
|
+
Traceback (most recent call last):
|
|
971
|
+
...
|
|
972
|
+
UnicodeDecodeError: 'ascii' codec can...t decode byte 0x80 in position 0: ordinal not in range(128)
|
|
973
|
+
"""
|
|
974
|
+
if not isinstance(s, bytes):
|
|
975
|
+
raise TypeError("s must be bytes")
|
|
976
|
+
|
|
977
|
+
if compress:
|
|
978
|
+
try:
|
|
979
|
+
s = comp.decompress(s)
|
|
980
|
+
except Exception as msg1:
|
|
981
|
+
try:
|
|
982
|
+
s = comp_other.decompress(s)
|
|
983
|
+
except Exception as msg2:
|
|
984
|
+
# Maybe data is uncompressed?
|
|
985
|
+
pass
|
|
986
|
+
|
|
987
|
+
unpickler = SageUnpickler(io.BytesIO(s), **kwargs)
|
|
988
|
+
global already_unpickled
|
|
989
|
+
ans = unpickler.load()
|
|
990
|
+
already_unpickled = {}
|
|
991
|
+
return ans
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
cdef bint make_pickle_jar = 'SAGE_PICKLE_JAR' in os.environ
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
def picklejar(obj, dir=None):
|
|
998
|
+
"""
|
|
999
|
+
Create pickled sobj of ``obj`` in ``dir``, with name the absolute
|
|
1000
|
+
value of the hash of the pickle of obj. This is used in conjunction
|
|
1001
|
+
with :func:`unpickle_all`.
|
|
1002
|
+
|
|
1003
|
+
To use this to test the whole Sage library right now, set the
|
|
1004
|
+
environment variable ``SAGE_PICKLE_JAR``, which will make it so
|
|
1005
|
+
:func:`dumps` will by default call :func:`picklejar` with the
|
|
1006
|
+
default dir. Once you do that and doctest Sage, you'll find that
|
|
1007
|
+
the ``DOT_SAGE/pickle_jar`` directory contains a bunch of
|
|
1008
|
+
pickled objects along with corresponding txt descriptions of them.
|
|
1009
|
+
Use the :func:`unpickle_all` to see if they unpickle later.
|
|
1010
|
+
|
|
1011
|
+
INPUT:
|
|
1012
|
+
|
|
1013
|
+
- ``obj`` -- a pickleable object
|
|
1014
|
+
|
|
1015
|
+
- ``dir`` -- string or ``None``; if ``None`` then ``dir`` defaults to
|
|
1016
|
+
``DOT_SAGE/pickle_jar``
|
|
1017
|
+
|
|
1018
|
+
EXAMPLES::
|
|
1019
|
+
|
|
1020
|
+
sage: dir = tmp_dir()
|
|
1021
|
+
sage: sage.misc.persist.picklejar(1, dir)
|
|
1022
|
+
sage: sage.misc.persist.picklejar('test', dir)
|
|
1023
|
+
sage: len(os.listdir(dir)) # Two entries (sobj and txt) for each object
|
|
1024
|
+
4
|
|
1025
|
+
|
|
1026
|
+
TESTS:
|
|
1027
|
+
|
|
1028
|
+
Test an unaccessible directory::
|
|
1029
|
+
|
|
1030
|
+
sage: import os, sys
|
|
1031
|
+
sage: s = os.stat(dir)
|
|
1032
|
+
sage: os.chmod(dir, 0o000)
|
|
1033
|
+
sage: try:
|
|
1034
|
+
....: uid = os.getuid()
|
|
1035
|
+
....: except AttributeError:
|
|
1036
|
+
....: uid = -1
|
|
1037
|
+
sage: if uid == 0:
|
|
1038
|
+
....: print("OK (cannot test this as root)")
|
|
1039
|
+
....: else:
|
|
1040
|
+
....: try:
|
|
1041
|
+
....: sage.misc.persist.picklejar(1, dir + '/noaccess')
|
|
1042
|
+
....: except PermissionError:
|
|
1043
|
+
....: print("OK (correctly raised PermissionError)")
|
|
1044
|
+
....: else:
|
|
1045
|
+
....: print("FAIL (did not raise an exception")
|
|
1046
|
+
OK...
|
|
1047
|
+
sage: os.chmod(dir, s.st_mode)
|
|
1048
|
+
"""
|
|
1049
|
+
if dir is None:
|
|
1050
|
+
from sage.env import DOT_SAGE
|
|
1051
|
+
dir = os.path.join(DOT_SAGE, 'pickle_jar')
|
|
1052
|
+
try:
|
|
1053
|
+
os.makedirs(dir)
|
|
1054
|
+
except OSError as err:
|
|
1055
|
+
# It is not an error if the directory exists
|
|
1056
|
+
import errno
|
|
1057
|
+
if not err.errno == errno.EEXIST:
|
|
1058
|
+
raise
|
|
1059
|
+
|
|
1060
|
+
global already_pickled
|
|
1061
|
+
s = comp.compress(SagePickler.dumps(obj))
|
|
1062
|
+
already_pickled = {}
|
|
1063
|
+
|
|
1064
|
+
typ = str(type(obj))
|
|
1065
|
+
name = ''.join([x if (x.isalnum() or x == '_') else '_' for x in typ])
|
|
1066
|
+
base = os.path.join(dir, name)
|
|
1067
|
+
if os.path.exists(base):
|
|
1068
|
+
i = 0
|
|
1069
|
+
while os.path.exists(f'{base}-{i}'):
|
|
1070
|
+
i += 1
|
|
1071
|
+
base += f'-{i}'
|
|
1072
|
+
|
|
1073
|
+
with open(base + '.sobj', 'wb') as fobj:
|
|
1074
|
+
fobj.write(s)
|
|
1075
|
+
|
|
1076
|
+
import sage.version
|
|
1077
|
+
stamp = dedent("""\
|
|
1078
|
+
type(obj) = {typ}
|
|
1079
|
+
version = {ver}
|
|
1080
|
+
obj = {obj}
|
|
1081
|
+
""".format(typ=typ, ver=sage.version.version, obj=obj))
|
|
1082
|
+
|
|
1083
|
+
with open(base + '.txt', 'w') as fobj:
|
|
1084
|
+
fobj.write(stamp)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def unpickle_all(target, debug=False, run_test_suite=False):
|
|
1088
|
+
"""
|
|
1089
|
+
Unpickle all ``.sobj`` files in a directory or tar archive.
|
|
1090
|
+
|
|
1091
|
+
INPUT:
|
|
1092
|
+
|
|
1093
|
+
- ``target`` -- string; the name of a directory or of a (possibly
|
|
1094
|
+
compressed) tar archive that contains a single directory of
|
|
1095
|
+
``.sobj`` files. The tar archive can be in any format that
|
|
1096
|
+
python's ``tarfile`` module understands; for example,
|
|
1097
|
+
``.tar.gz`` or ``.tar.bz2``.
|
|
1098
|
+
- ``debug`` -- boolean (default: ``False``)
|
|
1099
|
+
whether to report a stacktrace in case of failure
|
|
1100
|
+
- ``run_test_suite`` -- boolean (default: ``False``)
|
|
1101
|
+
whether to run ``TestSuite(x).run()`` on the unpickled objects
|
|
1102
|
+
|
|
1103
|
+
OUTPUT:
|
|
1104
|
+
|
|
1105
|
+
Typically, two lines are printed: the first reporting the number
|
|
1106
|
+
of successfully unpickled files, and the second reporting the
|
|
1107
|
+
number (zero) of failures. If there are failures, however, then a
|
|
1108
|
+
list of failed files will be printed before either of those lines,
|
|
1109
|
+
and the failure count will of course be nonzero.
|
|
1110
|
+
|
|
1111
|
+
.. WARNING::
|
|
1112
|
+
|
|
1113
|
+
You must only pass trusted data to this function, including tar
|
|
1114
|
+
archives. We use the "data" filter from PEP 706 if possible
|
|
1115
|
+
while extracting the archive, but even that is not a perfect
|
|
1116
|
+
solution, and it is only available since Python 3.11.4.
|
|
1117
|
+
|
|
1118
|
+
EXAMPLES::
|
|
1119
|
+
|
|
1120
|
+
sage: dir = tmp_dir()
|
|
1121
|
+
sage: sage.misc.persist.picklejar('hello', dir)
|
|
1122
|
+
sage: sage.misc.persist.unpickle_all(dir)
|
|
1123
|
+
Successfully unpickled 1 objects.
|
|
1124
|
+
Failed to unpickle 0 objects.
|
|
1125
|
+
"""
|
|
1126
|
+
import os.path
|
|
1127
|
+
import tarfile
|
|
1128
|
+
|
|
1129
|
+
ok_count = 0
|
|
1130
|
+
fail_count = 0
|
|
1131
|
+
failed = []
|
|
1132
|
+
tracebacks = []
|
|
1133
|
+
|
|
1134
|
+
if os.path.isfile(target) and tarfile.is_tarfile(target):
|
|
1135
|
+
import tempfile
|
|
1136
|
+
with tempfile.TemporaryDirectory() as T:
|
|
1137
|
+
# Extract the tarball to a temporary directory. The "data"
|
|
1138
|
+
# filter only became available in python-3.11.4. See PEP
|
|
1139
|
+
# 706 for background.
|
|
1140
|
+
with tarfile.open(target) as tf:
|
|
1141
|
+
if hasattr(tarfile, "data_filter"):
|
|
1142
|
+
tf.extractall(T, filter='data')
|
|
1143
|
+
else:
|
|
1144
|
+
tf.extractall(T)
|
|
1145
|
+
|
|
1146
|
+
# Ensure that the tarball contained exactly one thing, a
|
|
1147
|
+
# directory.
|
|
1148
|
+
bad_tarball_msg = "tar archive must contain only a single directory"
|
|
1149
|
+
contents = os.listdir(T)
|
|
1150
|
+
if len(contents) != 1:
|
|
1151
|
+
raise ValueError(bad_tarball_msg)
|
|
1152
|
+
|
|
1153
|
+
dir = os.path.join(T, contents[0])
|
|
1154
|
+
if not os.path.isdir(dir):
|
|
1155
|
+
raise ValueError(bad_tarball_msg)
|
|
1156
|
+
|
|
1157
|
+
# If everything looks OK, start this function over again
|
|
1158
|
+
# inside the extracted directory. Note: PEP 343 says the
|
|
1159
|
+
# temporary directory will be cleaned up even when the
|
|
1160
|
+
# "with" block is exited via a "return" statement. But
|
|
1161
|
+
# also note that "return" doesn't happen until the
|
|
1162
|
+
# recursive call to unpickle_all() has completed.
|
|
1163
|
+
return unpickle_all(dir, debug, run_test_suite)
|
|
1164
|
+
|
|
1165
|
+
if not os.path.isdir(target):
|
|
1166
|
+
raise ValueError("target is neither a directory nor a tar archive")
|
|
1167
|
+
|
|
1168
|
+
for A in sorted(os.listdir(target)):
|
|
1169
|
+
f = os.path.join(target, A)
|
|
1170
|
+
if os.path.isfile(f) and f.endswith('.sobj'):
|
|
1171
|
+
try:
|
|
1172
|
+
obj = load(f)
|
|
1173
|
+
if run_test_suite:
|
|
1174
|
+
TestSuite(obj).run(catch = False)
|
|
1175
|
+
ok_count += 1
|
|
1176
|
+
except Exception:
|
|
1177
|
+
fail_count += 1
|
|
1178
|
+
if run_test_suite:
|
|
1179
|
+
print(" * unpickle failure: TestSuite(load('%s')).run()" % f)
|
|
1180
|
+
else:
|
|
1181
|
+
print(" * unpickle failure: load('%s')" % f)
|
|
1182
|
+
from traceback import print_exc
|
|
1183
|
+
print_exc()
|
|
1184
|
+
failed.append(A)
|
|
1185
|
+
if debug:
|
|
1186
|
+
tracebacks.append(sys.exc_info())
|
|
1187
|
+
|
|
1188
|
+
if failed:
|
|
1189
|
+
print("Failed:\n%s" % ('\n'.join(failed)))
|
|
1190
|
+
print("Successfully unpickled %s objects." % ok_count)
|
|
1191
|
+
print("Failed to unpickle %s objects." % fail_count)
|
|
1192
|
+
if debug:
|
|
1193
|
+
return tracebacks
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
def make_None(*args, **kwds):
|
|
1197
|
+
"""
|
|
1198
|
+
Do nothing and return ``None``. Used for overriding pickles when
|
|
1199
|
+
that pickle is no longer needed.
|
|
1200
|
+
|
|
1201
|
+
EXAMPLES::
|
|
1202
|
+
|
|
1203
|
+
sage: from sage.misc.persist import make_None
|
|
1204
|
+
sage: print(make_None(42, pi, foo='bar')) # needs sage.symbolic
|
|
1205
|
+
None
|
|
1206
|
+
"""
|
|
1207
|
+
return None
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
def load_sage_object(cls, dic): # not used
|
|
1211
|
+
X = cls.__new__(cls)
|
|
1212
|
+
try:
|
|
1213
|
+
X.__setstate__(dic)
|
|
1214
|
+
except AttributeError:
|
|
1215
|
+
X.__dict__ = dic
|
|
1216
|
+
return X
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
def load_sage_element(cls, parent, dic_pic):
|
|
1220
|
+
X = cls.__new__(cls)
|
|
1221
|
+
X._set_parent(parent)
|
|
1222
|
+
X.__dict__ = SageUnpickler.loads(dic_pic)
|
|
1223
|
+
return X
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
def db(name):
|
|
1227
|
+
r"""
|
|
1228
|
+
Load object with given name from the Sage database. Use x.db(name)
|
|
1229
|
+
or db_save(x, name) to save objects to the database.
|
|
1230
|
+
|
|
1231
|
+
The database directory is ``$HOME/.sage/db``.
|
|
1232
|
+
"""
|
|
1233
|
+
deprecation(39012, "Directly use pickle/unpickle instead of db/db_save.")
|
|
1234
|
+
|
|
1235
|
+
from sage.misc.misc import SAGE_DB
|
|
1236
|
+
return load('%s/%s' % (SAGE_DB, name))
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
def db_save(x, name=None):
|
|
1240
|
+
r"""
|
|
1241
|
+
Save x to the Sage database.
|
|
1242
|
+
|
|
1243
|
+
The database directory is ``$HOME/.sage/db``.
|
|
1244
|
+
"""
|
|
1245
|
+
deprecation(39012, "Directly use pickle/unpickle instead of db/db_save.")
|
|
1246
|
+
|
|
1247
|
+
try:
|
|
1248
|
+
x.db(name)
|
|
1249
|
+
except AttributeError:
|
|
1250
|
+
from sage.misc.misc import SAGE_DB
|
|
1251
|
+
save(x, '%s/%s' % (SAGE_DB, name))
|