From 457c650d21984284ee6fbdb21b91681322d81746 Mon Sep 17 00:00:00 2001 From: Jerry James Date: Wed, 6 Jul 2022 11:11:16 -0600 Subject: [PATCH] Add patch for python 3.11 compatibility (bz 2099168) - Add workaround for symlink/directory conflict (bz 2097773) - Modify the jmol patch to timeout after 5 minutes. This is a workaround for recently observed jmol hangs, but is not a proper fix. --- sagemath-jmol.patch | 22 +- sagemath-python3.11.patch | 427 ++++++++++++++++++++++++++++++++++++++ sagemath.spec | 35 +++- 3 files changed, 472 insertions(+), 12 deletions(-) create mode 100644 sagemath-python3.11.patch diff --git a/sagemath-jmol.patch b/sagemath-jmol.patch index c339a61..97ef49d 100644 --- a/sagemath-jmol.patch +++ b/sagemath-jmol.patch @@ -1,6 +1,6 @@ diff -up src/sage/interfaces/jmoldata.py.orig src/sage/interfaces/jmoldata.py ---- src/sage/interfaces/jmoldata.py.orig 2021-05-09 16:00:11.000000000 -0600 -+++ src/sage/interfaces/jmoldata.py 2021-05-28 11:36:18.844565672 -0600 +--- src/sage/interfaces/jmoldata.py.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/interfaces/jmoldata.py 2022-07-06 11:03:43.155375965 -0600 @@ -148,8 +148,6 @@ class JmolData(SageObject): sage: print(os.path.exists(testfile)) # optional -- java True @@ -10,16 +10,24 @@ diff -up src/sage/interfaces/jmoldata.py.orig src/sage/interfaces/jmoldata.py target_native = targetfile if sys.platform == 'cygwin': -@@ -173,10 +171,8 @@ class JmolData(SageObject): +@@ -171,12 +169,14 @@ class JmolData(SageObject): + with open(scratchout, 'w') as jout: + # Now call the java application and write the file. env = dict(os.environ) - env['LC_ALL'] = 'C' - env['LANG'] = 'C' +- env['LC_ALL'] = 'C' +- env['LANG'] = 'C' - subprocess.call(["java", "-Xmx512m", "-Djava.awt.headless=true", - "-jar", jmolpath, "-iox", "-g", size_arg, - "-J", launchscript, "-j", imagescript], - stdout=jout, stderr=jout, env=env) -+ subprocess.call(["jmol", "-n", "-g", size_arg, -+ "-J", launchscript, "-j", imagescript], stdout=jout) ++ env['LC_ALL'] = 'C.UTF-8' ++ env['LANG'] = 'C.UTF-8' ++ try: ++ subprocess.call(["jmol", "-n", "-g", size_arg, ++ "-J", launchscript, "-j", imagescript], ++ timeout=300, stdout=jout, stderr=jout, env=env) ++ except: ++ pass if not os.path.isfile(targetfile): raise RuntimeError(f"Jmol failed to create file {targetfile}: {Path(scratchout).read_text()}") os.unlink(scratchout) diff --git a/sagemath-python3.11.patch b/sagemath-python3.11.patch new file mode 100644 index 0000000..10c2349 --- /dev/null +++ b/sagemath-python3.11.patch @@ -0,0 +1,427 @@ +diff -up src/sage/cpython/cython_metaclass.h.orig src/sage/cpython/cython_metaclass.h +--- src/sage/cpython/cython_metaclass.h.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/cpython/cython_metaclass.h 2022-06-29 11:07:37.296150769 -0600 +@@ -66,7 +66,7 @@ static CYTHON_INLINE int Sage_PyType_Rea + } + + /* Now, set t.__class__ to metaclass */ +- Py_TYPE(t) = metaclass; ++ Py_SET_TYPE(t, metaclass); + PyType_Modified(t); + } + else +diff -up src/sage/cpython/dict_del_by_value.pyx.orig src/sage/cpython/dict_del_by_value.pyx +--- src/sage/cpython/dict_del_by_value.pyx.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/cpython/dict_del_by_value.pyx 2022-06-30 12:06:01.148346330 -0600 +@@ -25,7 +25,7 @@ from weakref import KeyedRef + from cpython.list cimport PyList_New + from cpython cimport Py_XINCREF, Py_XDECREF + +-from libc.stdint cimport int8_t, int16_t, int32_t, int64_t ++from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, uint8_t, uint32_t + cdef extern from "Python.h": + ctypedef struct PyDictKeysObject + +@@ -45,8 +45,6 @@ cdef extern from "Python.h": + #(this file is not exported from CPython, so we need to be + #careful the definitions are in step with what happens there. + +-ctypedef void* dict_lookup_func # Precise definition not needed +- + ctypedef union IndexBlock: + int8_t as_1[8] + int16_t as_2[4] +@@ -55,8 +53,10 @@ ctypedef union IndexBlock: + + ctypedef struct MyPyDictKeysObject: + Py_ssize_t dk_refcnt +- Py_ssize_t dk_size +- dict_lookup_func dk_lookup ++ uint8_t dk_log2_size ++ uint8_t dk_log2_index_bytes ++ uint8_t dk_kind ++ uint32_t dk_version + Py_ssize_t dk_usable + Py_ssize_t dk_nentries + IndexBlock dk_indices +@@ -66,46 +66,65 @@ ctypedef struct PyDictKeyEntry: + PyObject * me_key + PyObject * me_value + ++ctypedef struct PyDictUnicodeEntry: ++ PyObject * me_key ++ PyObject * me_value ++ + cdef Py_ssize_t DKIX_EMPTY = -1 + cdef Py_ssize_t DKIX_DUMMY = -2 + cdef Py_ssize_t DKIX_ERROR = -3 ++cdef Py_ssize_t DKIX_KEY_CHANGED = -4 ++ ++ctypedef enum DictKeysKind: ++ DICT_KEYS_GENERAL = 0, ++ DICT_KEYS_UNICODE = 1, ++ DICT_KEYS_SPLIT = 2 + + ##### + #These routines are copied from CPython's Object/dictobject.c + #in order to access PyDictKeysObject fields + ++cdef inline Py_ssize_t DK_SIZE(MyPyDictKeysObject *keys): ++ return 1L << keys.dk_log2_size ++ + cdef inline int DK_IXSIZE(MyPyDictKeysObject *keys): +- cdef Py_ssize_t s = keys.dk_size +- if s <= 0xff: ++ cdef uint8_t s = keys.dk_log2_size ++ if s <= 7: + return 1 +- elif s <= 0xffff: ++ if s <= 15: + return 2 +- elif s <= 0xffffffff: ++ if sizeof(void *) <= 4 or s <= 31: + return 4 + else: + return 8 + + cdef inline PyDictKeyEntry * DK_ENTRIES(MyPyDictKeysObject *keys): +- return &(keys.dk_indices.as_1[keys.dk_size * DK_IXSIZE(keys)]) ++ return &(keys.dk_indices.as_1[1UL << keys.dk_log2_index_bytes]) ++ ++cdef inline PyDictUnicodeEntry * DK_UNICODE_ENTRIES(MyPyDictKeysObject *keys): ++ return &(keys.dk_indices.as_1[1UL << keys.dk_log2_index_bytes]) ++ ++cdef inline bint DK_IS_UNICODE(MyPyDictKeysObject *keys): ++ return keys.dk_kind != DICT_KEYS_GENERAL + + cdef inline Py_ssize_t dk_get_index(MyPyDictKeysObject *keys, Py_ssize_t i): +- cdef Py_ssize_t s = keys.dk_size +- if s <= 0xff: ++ cdef uint8_t s = keys.dk_log2_size ++ if s < 8: + return keys.dk_indices.as_1[i] +- elif s <= 0xffff: ++ elif s < 16: + return keys.dk_indices.as_2[i] +- elif s <= 0xffffffff: ++ elif sizeof(void *) <= 4 or s < 32: + return keys.dk_indices.as_4[i] + else: + return keys.dk_indices.as_8[i] + + cdef inline void dk_set_index(MyPyDictKeysObject *keys, Py_ssize_t i, Py_ssize_t ix): +- cdef Py_ssize_t s = keys.dk_size +- if s <= 0xff: ++ cdef uint8_t s = keys.dk_log2_size ++ if s < 8: + keys.dk_indices.as_1[i] = ix +- elif s <= 0xffff: ++ elif s < 16: + keys.dk_indices.as_2[i] = ix +- elif s <= 0xffffffff: ++ elif sizeof(void *) <= 4 or s < 32: + keys.dk_indices.as_4[i] = ix + else: + keys.dk_indices.as_8[i] = ix +@@ -113,22 +132,7 @@ cdef inline void dk_set_index(MyPyDictKe + #End of replication of Object/dictobject.c + ###### + +-cdef dict_lookup_func lookdict +- +-cdef dict_lookup_func DK_LOOKUP(PyDictObject *mp): +- return ((mp.ma_keys)).dk_lookup +- +-def init_lookdict(): +- global lookdict +- # A dict which a non-string key uses the generic "lookdict" +- # as lookup function +- cdef object D = {} +- D[0] = 0 +- lookdict = DK_LOOKUP(D) +- +-init_lookdict() +- +-cdef int del_dictitem_by_exact_value(PyDictObject *mp, PyObject *value, Py_hash_t hash) except -1: ++cdef int del_dictitem_by_exact_value(PyDictObject *mp, PyObject *value, Py_hash_t hashval) except -1: + """ + This is used in callbacks for the weak values of :class:`WeakValueDictionary`. + +@@ -179,47 +183,72 @@ cdef int del_dictitem_by_exact_value(PyD + """ + keys = (mp.ma_keys) + cdef size_t perturb +- cdef size_t mask = keys.dk_size-1 +- cdef PyDictKeyEntry *entries = DK_ENTRIES(keys) ++ cdef size_t mask = DK_SIZE(keys)-1 ++ cdef PyDictUnicodeEntry *uc_entries ++ cdef PyDictUnicodeEntry *uc_ep ++ cdef PyDictKeyEntry *entries + cdef PyDictKeyEntry *ep +- ++ + if mp.ma_values is not NULL: + raise TypeError("del_dictitem_by_exact_value cannot be applied to a shared key dict") + +- cdef size_t i = hash & mask ++ cdef size_t i = hashval & mask + ix = dk_get_index(keys, i) + + if ix == DKIX_EMPTY: + # key not found + return 0 + +- ep = &(entries[ix]) +- perturb = hash +- while (ep.me_value != value or ep.me_hash != hash): +- perturb = perturb >> 5 #this is the value of PERTURB_SHIFT +- i = mask & (i * 5 + perturb + 1) +- ix = dk_get_index(keys, i) +- if ix == DKIX_EMPTY: +- # key not found +- return 0 +- ep = &(entries[ix]) ++ if DK_IS_UNICODE(keys): ++ uc_entries = DK_UNICODE_ENTRIES(keys) ++ uc_ep = &(uc_entries[ix]) ++ perturb = hashval ++ while (uc_ep.me_value != value or hash(uc_ep.me_key) != hashval): ++ perturb = perturb >> 5 #this is the value of PERTURB_SHIFT ++ i = mask & (i * 5 + perturb + 1) ++ ix = dk_get_index(keys, i) ++ if ix == DKIX_EMPTY: ++ # key not found ++ return 0 ++ uc_ep = &(uc_entries[ix]) + +- # We need the lookup function to be the generic lookdict, otherwise +- # deletions may not work correctly +- keys.dk_lookup = lookdict ++ T = PyList_New(2) ++ PyList_SetItem(T, 0, uc_ep.me_key) ++ PyList_SetItem(T, 1, uc_ep.me_value) ++ uc_ep.me_key = NULL ++ uc_ep.me_value = NULL ++ mp.ma_used -= 1 ++ dk_set_index(keys, i, DKIX_DUMMY) ++ #We have transferred the to-be-deleted references to the list T ++ #we now delete the list so that the actual decref happens through a ++ #deallocation routine that uses the Python Trashcan macros to ++ #avoid stack overflow in deleting deep structures. ++ del T ++ else: ++ entries = DK_ENTRIES(keys) ++ ep = &(entries[ix]) ++ perturb = hashval ++ while (ep.me_value != value or ep.me_hash != hashval): ++ perturb = perturb >> 5 #this is the value of PERTURB_SHIFT ++ i = mask & (i * 5 + perturb + 1) ++ ix = dk_get_index(keys, i) ++ if ix == DKIX_EMPTY: ++ # key not found ++ return 0 ++ ep = &(entries[ix]) + +- T = PyList_New(2) +- PyList_SetItem(T, 0, ep.me_key) +- PyList_SetItem(T, 1, ep.me_value) +- ep.me_key = NULL +- ep.me_value = NULL +- mp.ma_used -= 1 +- dk_set_index(keys, i, DKIX_DUMMY) +- #We have transferred the to-be-deleted references to the list T +- #we now delete the list so that the actual decref happens through a +- #deallocation routine that uses the Python Trashcan macros to +- #avoid stack overflow in deleting deep structures. +- del T ++ T = PyList_New(2) ++ PyList_SetItem(T, 0, ep.me_key) ++ PyList_SetItem(T, 1, ep.me_value) ++ ep.me_key = NULL ++ ep.me_value = NULL ++ mp.ma_used -= 1 ++ dk_set_index(keys, i, DKIX_DUMMY) ++ #We have transferred the to-be-deleted references to the list T ++ #we now delete the list so that the actual decref happens through a ++ #deallocation routine that uses the Python Trashcan macros to ++ #avoid stack overflow in deleting deep structures. ++ del T + + def test_del_dictitem_by_exact_value(D, value, h): + """ +diff -up src/sage/libs/gmp/pylong.pyx.orig src/sage/libs/gmp/pylong.pyx +--- src/sage/libs/gmp/pylong.pyx.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/libs/gmp/pylong.pyx 2022-06-29 12:44:27.983218616 -0600 +@@ -33,6 +33,7 @@ from .mpz cimport * + + cdef extern from *: + Py_ssize_t* Py_SIZE_PTR "&Py_SIZE"(object) ++ void __Pyx_SET_SIZE(object, Py_ssize_t) + int hash_bits """ + #ifdef _PyHASH_BITS + _PyHASH_BITS /* Python 3 */ +@@ -57,10 +58,8 @@ cdef mpz_get_pylong_large(mpz_srcptr z): + mpz_export(L.ob_digit, NULL, + -1, sizeof(digit), 0, PyLong_nails, z) + if mpz_sgn(z) < 0: +- # Set correct size (use a pointer to hack around Cython's +- # non-support for lvalues). +- sizeptr = Py_SIZE_PTR(L) +- sizeptr[0] = -pylong_size ++ # Set correct size ++ __Pyx_SET_SIZE(L, -pylong_size) + return L + + +diff -up src/sage/misc/decorators.py.orig src/sage/misc/decorators.py +--- src/sage/misc/decorators.py.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/misc/decorators.py 2022-06-29 14:02:56.995436952 -0600 +@@ -31,8 +31,7 @@ from functools import (partial, update_w + from copy import copy + + from sage.misc.sageinspect import (sage_getsource, sage_getsourcelines, +- sage_getargspec) +-from inspect import ArgSpec ++ sage_getargspec, ArgSpec) + + + def sage_wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): +diff -up src/sage/misc/sageinspect.py.orig src/sage/misc/sageinspect.py +--- src/sage/misc/sageinspect.py.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/misc/sageinspect.py 2022-06-29 14:07:56.804147321 -0600 +@@ -119,6 +119,7 @@ import functools + import os + import tokenize + import re ++from collections import namedtuple + from sage.env import SAGE_LIB + + try: +@@ -126,6 +127,8 @@ try: + except ImportError: + pass + ++ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults') ++ + + def is_function_or_cython_function(obj): + """ +@@ -348,7 +351,7 @@ def _extract_embedded_signature(docstrin + docstring = L[1] if len(L) > 1 else '' # Remove first line, keep the rest + def_string = "def " + name + signature + ": pass" + try: +- return docstring, inspect.ArgSpec(*_sage_getargspec_cython(def_string)) ++ return docstring, ArgSpec(*_sage_getargspec_cython(def_string)) + except SyntaxError: + docstring = os.linesep.join(L) + return docstring, None +@@ -1092,7 +1095,7 @@ def _sage_getargspec_from_ast(source): + + OUTPUT: + +- - an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple ++ - an instance of :obj:`ArgSpec`, i.e., a named tuple + + EXAMPLES:: + +@@ -1124,8 +1127,7 @@ def _sage_getargspec_from_ast(source): + vararg = getattr(ast_args.vararg, 'arg', None) + kwarg = getattr(ast_args.kwarg, 'arg', None) + +- return inspect.ArgSpec(args, vararg, kwarg, +- tuple(defaults) if defaults else None) ++ return ArgSpec(args, vararg, kwarg, tuple(defaults) if defaults else None) + + + def _sage_getargspec_cython(source): +@@ -1141,7 +1143,7 @@ def _sage_getargspec_cython(source): + + OUTPUT: + +- - an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple ++ - an instance of :obj:`ArgSpec`, i.e., a named tuple + + EXAMPLES:: + +@@ -1605,11 +1607,11 @@ def sage_getargspec(obj): + return sage_getargspec(obj.__call__) + if isinstance(obj, (lazy_attribute, AbstractMethod)): + source = sage_getsource(obj) +- return inspect.ArgSpec(*_sage_getargspec_cython(source)) ++ return ArgSpec(*_sage_getargspec_cython(source)) + if not callable(obj): + raise TypeError("obj is not a code object") + try: +- return inspect.ArgSpec(*obj._sage_argspec_()) ++ return ArgSpec(*obj._sage_argspec_()) + except (AttributeError, TypeError): + pass + # If we are lucky, the function signature is embedded in the docstring. +@@ -1625,7 +1627,7 @@ def sage_getargspec(obj): + # Note that this may give a wrong result for the constants! + try: + args, varargs, varkw = inspect.getargs(obj.__code__) +- return inspect.ArgSpec(args, varargs, varkw, obj.__defaults__) ++ return ArgSpec(args, varargs, varkw, obj.__defaults__) + except (TypeError, AttributeError): + pass + if isclassinstance(obj): +@@ -1660,7 +1662,7 @@ def sage_getargspec(obj): + except TypeError: # happens for Python builtins + source = '' + if source: +- return inspect.ArgSpec(*_sage_getargspec_cython(source)) ++ return ArgSpec(*_sage_getargspec_cython(source)) + else: + func_obj = obj + +@@ -1673,7 +1675,7 @@ def sage_getargspec(obj): + except TypeError: # arg is not a code object + # The above "hopefully" was wishful thinking: + try: +- return inspect.ArgSpec(*_sage_getargspec_cython(sage_getsource(obj))) ++ return ArgSpec(*_sage_getargspec_cython(sage_getsource(obj))) + except TypeError: # This happens for Python builtins + # The best we can do is to return a generic argspec + args = [] +@@ -1683,7 +1685,7 @@ def sage_getargspec(obj): + defaults = func_obj.__defaults__ + except AttributeError: + defaults = None +- return inspect.ArgSpec(args, varargs, varkw, defaults) ++ return ArgSpec(args, varargs, varkw, defaults) + + + def formatannotation(annotation, base_module=None): +@@ -1754,19 +1756,7 @@ def sage_formatargspec(args, varargs=Non + :func:`sage_getargspec`. Since :func:`sage_getargspec` works for + Cython functions while Python's inspect module does not, it makes + sense to keep this function for formatting instances of +- ``inspect.ArgSpec``. +- +- EXAMPLES:: +- +- sage: from sage.misc.sageinspect import sage_formatargspec +- sage: from inspect import formatargspec # deprecated in Python 3 +- sage: args = ['a', 'b', 'c'] +- sage: defaults = [3] +- sage: sage_formatargspec(args, defaults=defaults) +- '(a, b, c=3)' +- sage: import warnings; warnings.simplefilter('ignore') # ignore DeprecationWarning +- sage: formatargspec(args, defaults=defaults) == sage_formatargspec(args, defaults=defaults) +- True ++ ``ArgSpec``. + """ + def formatargandannotation(arg): + result = formatarg(arg) +diff -up src/sage/symbolic/ginac/numeric.cpp.orig src/sage/symbolic/ginac/numeric.cpp +--- src/sage/symbolic/ginac/numeric.cpp.orig 2022-05-15 16:11:11.000000000 -0600 ++++ src/sage/symbolic/ginac/numeric.cpp 2022-06-24 14:58:32.506790725 -0600 +@@ -52,7 +52,6 @@ + #define register + #define PY_SSIZE_T_CLEAN + #include +-#include + #include "flint/fmpz.h" + #include "flint/fmpz_factor.h" + diff --git a/sagemath.spec b/sagemath.spec index 81117da..076bffa 100644 --- a/sagemath.spec +++ b/sagemath.spec @@ -84,7 +84,7 @@ Name: sagemath Summary: A free open-source mathematics software system Version: 9.6 -Release: 2%{?dist} +Release: 3%{?dist} # The file ${SAGE_ROOT}/COPYING.txt is the upstream license breakdown file # Additionally, every $files section has a comment with the license name # before files with that license @@ -178,6 +178,9 @@ Patch21: %{name}-infinite-recursion.patch # Fix a use-after-free bug Patch22: %{name}-use-after-free.patch +# Adapt to changes in python 3.11 +Patch23: %{name}-python3.11.patch + BuildRequires: 4ti2 BuildRequires: 4ti2-devel BuildRequires: appstream @@ -981,6 +984,7 @@ popd %patch20 %patch21 %patch22 +%patch23 sed -i 's|@@SAGE_LOCAL@@|%{SAGE_LOCAL}|' src/sage/env.py @@ -1030,6 +1034,9 @@ sed -i 's/3\.11\.0/3.12.0/g' configure # Allow use of libfplll 5.4.2 sed -i 's/5\.4\.1/5.4.2/g' configure +# Allow use of eclib 20220621 +sed -i 's/20210625/20220621/g' configure + # Do not build with -march=native sed -i 's/CFLAGS_MARCH="-march=native"/CFLAGS_MARCH=""/' configure @@ -1037,7 +1044,6 @@ sed -i 's/CFLAGS_MARCH="-march=native"/CFLAGS_MARCH=""/' configure ######################################################################## %build export LC_ALL=C.UTF-8 -export CC=%{__cc} export CPPFLAGS="-I%{_includedir}/4ti2 -I%{_includedir}/arb -I%{_includedir}/cddlib" export ECMBIN=%{_bindir}/gmp-ecm export SAGE_ROOT=%{buildroot}%{SAGE_ROOT} @@ -1678,10 +1684,9 @@ mkdir -p %{SAGE_LOCAL}/var/lib/sage/installed/database_cremona_ellcurve-%{cremon rm -fr %{SAGE_LOCAL}/var/lib/sage/installed/database_cremona_ellcurve-%{cremona_ver} %if %{with docs} -######################################################################## -# Fix directory/symlink conflicts introduced by fix for bz 1875606. -# This can be removed when Fedora 36 reaches EOL. %pretrans -n sagemath-doc-en -p +-- Fix directory/symlink conflicts introduced by fix for bz 1875606. +-- This can be removed when Fedora 36 reaches EOL. paths = { "%{SAGE_DOC}/html/en/_static/config", "%{SAGE_DOC}/html/en/_static/extensions", @@ -1765,6 +1770,20 @@ for _, path in ipairs(paths) do end end end + +-- Fix directory/symlink conflicts introduced by sagemath 3.5 -> 3.6 upgrade. +-- This can be removed when Fedora 40 reaches EOL. +paths2 = { + "%{SAGE_DOC}/html/en/reference/plot3d/_static", + "%{SAGE_DOC}/html/en/reference/repl/_static" +} + +for _, path in ipairs(paths2) do + st = posix.stat(path) + if st and st.type == "link" then + os.remove(path) + end +end %endif @@ -2021,6 +2040,12 @@ end ######################################################################## %changelog +* Tue Jul 5 2022 Jerry James - 9.6-3 +- Add patch for python 3.11 compatibility (bz 2099168) +- Add workaround for symlink/directory conflict (bz 2097773) +- Modify the jmol patch to timeout after 5 minutes. This is a workaround for + recently observed jmol hangs, but is not a proper fix. + * Tue Jun 21 2022 Python Maint - 9.6-2 - Rebuilt for Python 3.11