diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 0954a68..7929eb6 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -27,6 +27,7 @@ jobs: python-version: - "3.12" - "3.13" + - "3.14" extra-doctest-ignore: - "" # The OrderedDict __str__ method changed in Python 3.12 so doctests in the ytools.py module will fail @@ -47,9 +48,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2495621..db7c272 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -32,15 +32,15 @@ jobs: - windows-2022 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" - name: Build wheels - uses: pypa/cibuildwheel@v2.23.3 + uses: pypa/cibuildwheel@v3.4.0 - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} path: wheelhouse/*.whl @@ -51,9 +51,9 @@ jobs: needs: [build_wheels] steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" - name: Install dependencies and build sdist @@ -64,13 +64,13 @@ jobs: $PYTHON_VENV/bin/pip install -v build $PYTHON_VENV/bin/python -m build -s . - name: Download wheels - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: cibw-wheels-* merge-multiple: true path: dist/ - name: Upload wheels and sdist - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: wheels-and-sdist path: dist/ @@ -79,4 +79,4 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: | python -m pip install --upgrade packaging twine - python -m twine upload --verbose dist/* + # python -m twine upload --verbose dist/* diff --git a/pyproject.toml b/pyproject.toml index 3b7e10c..2be4c4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,8 +81,8 @@ line-length = 88 build-frontend = "build" archs = ["auto64"] skip = [ - "pp*", # PyPy - "*musl*", # musllinux + "cp3??t-*", # Free-threading on all platforms + "*musl*", # musllinux ] diff --git a/pyyeti/guitools.py b/pyyeti/guitools.py index c391897..39cc42d 100644 --- a/pyyeti/guitools.py +++ b/pyyeti/guitools.py @@ -9,10 +9,16 @@ import os import sys from functools import wraps -import tkinter as tk -from tkinter import filedialog -import tkinter.font as tkFont -from tkinter import ttk + +try: + import tkinter as tk + from tkinter import filedialog + import tkinter.font as tkFont + from tkinter import ttk +except ImportError: + HAVE_TKINTER = False +else: + HAVE_TKINTER = True LASTOPENDIR = None @@ -106,6 +112,9 @@ def askopenfilename(title=None, filetypes=None, initialdir=None): # pragma: no filename = guitools.askopenfilename(filetypes=filetypes) """ + if not HAVE_TKINTER: + msg = "tkinter not available, cannot create GUI dialog" + raise ImportError(msg) global LASTOPENDIR root = tk.Tk() root.withdraw() @@ -158,6 +167,9 @@ def asksaveasfilename(title=None, filetypes=None, initialdir=None): # pragma: n filename = guitools.asksaveasfilename(filetypes=filetypes) """ + if not HAVE_TKINTER: + msg = "tkinter not available, cannot create GUI dialog" + raise ImportError(msg) global LASTSAVEDIR root = tk.Tk() root.withdraw() @@ -357,6 +369,10 @@ def __init__( topstring : string; optional String to print above table """ + if not HAVE_TKINTER: + msg = "tkinter not available, cannot create GUI dialog" + raise ImportError(msg) + self.root = tk.Tk() self.root.title(title) self.tree = None diff --git a/pyyeti/nastran/n2p.py b/pyyeti/nastran/n2p.py index 8517b2a..0c181d5 100644 --- a/pyyeti/nastran/n2p.py +++ b/pyyeti/nastran/n2p.py @@ -460,7 +460,7 @@ def replace_basic_cs(uset, new_cs_id, new_cs_in_basic=None): # extract the GRID part of the uset table to a numpy array: grids = uset_new.index.get_level_values("dof") > 0 - xyz = uset_new.loc[grids, "x":"z"].values # .copy() + xyz = uset_new.loc[grids, "x":"z"].values.copy() nrows = xyz.shape[0] current_cs_ids = xyz[1::6, 0].astype(int) diff --git a/pyyeti/tests/test_cb.py b/pyyeti/tests/test_cb.py index 0c1575a..c52f143 100644 --- a/pyyeti/tests/test_cb.py +++ b/pyyeti/tests/test_cb.py @@ -4,6 +4,7 @@ from io import StringIO import tempfile import os +import re import inspect from pyyeti import cb, ytools, nastran from pyyeti.nastran import op2, n2p, op4 @@ -694,7 +695,8 @@ def test_cbcoordchk(): assert abs(chk0.maxerr).max() < 1e-5 # a case where the refpoint_chk should be 'fail': - with pytest.warns(la.LinAlgWarning, match=r"Ill\-conditioned matrix"): + regex = re.compile(r"ill\-conditioned matrix", re.IGNORECASE) + with pytest.warns(la.LinAlgWarning, match=regex): chk2 = cb.cbcoordchk(kaa, b, [25, 26, 27, 31, 32, 33], verbose=False) assert chk2.refpoint_chk == "fail" @@ -1045,7 +1047,8 @@ def test_cbcoordchk3(): assert abs(chk0.maxerr).max() < 1e-5 # a case where the refpoint_chk should be 'fail': - with pytest.warns(la.LinAlgWarning, match=r"Ill\-conditioned matrix"): + regex = re.compile(r"ill\-conditioned matrix", re.IGNORECASE) + with pytest.warns(la.LinAlgWarning, match=regex): chk2 = cb.cbcoordchk(kaa, b, [25, 26, 27, 31, 32, 33], verbose=False) assert chk2.refpoint_chk == "fail" diff --git a/pyyeti/tests/test_fdepsd.py b/pyyeti/tests/test_fdepsd.py index ef991b8..b67860d 100644 --- a/pyyeti/tests/test_fdepsd.py +++ b/pyyeti/tests/test_fdepsd.py @@ -23,7 +23,7 @@ def compare(fde1, fde2): def test_fdepsd_absacce(): - np.random.seed(1) + rng = np.random.default_rng(1) TF = 60 # make a 60 second signal sp = 1.0 spec = np.array([[20, sp], [50, sp]]) @@ -35,6 +35,7 @@ def test_fdepsd_absacce(): df=1 / TF, winends=dict(portion=10), gettime=True, + rng=rng, ) freq = np.arange(30.0, 50.1) q = 25 @@ -169,9 +170,9 @@ def test_fdepsd_error(): def test_ski_slope(): - # np.random.seed(1) + rng = np.random.default_rng(1) spec = np.array([[20.0, 1.0], [100.0, 1.0], [150.0, 10.0], [1000.0, 10.0]]) - sig, sr = psd.psd2time(spec, 20, 1000) + sig, sr = psd.psd2time(spec, 20, 1000, rng=rng) sig[0] = sig.max() diff --git a/pyyeti/tests/test_guitools.py b/pyyeti/tests/test_guitools.py new file mode 100644 index 0000000..a17b76b --- /dev/null +++ b/pyyeti/tests/test_guitools.py @@ -0,0 +1,31 @@ +import pytest +from pyyeti import guitools + + +@pytest.mark.parametrize("read", [True, False]) +def test_get_file_name_no_tkinter(read, monkeypatch): + monkeypatch.setattr(guitools, "HAVE_TKINTER", False) + with pytest.raises(ImportError, match="tkinter not available.*cannot create GUI"): + guitools.get_file_name(None, read) + + +def test_askopenfilename_no_tkinter(monkeypatch): + monkeypatch.setattr(guitools, "HAVE_TKINTER", False) + with pytest.raises(ImportError, match="tkinter not available.*cannot create GUI"): + guitools.askopenfilename() + + +def test_asksaveasfilename_no_tkinter(monkeypatch): + monkeypatch.setattr(guitools, "HAVE_TKINTER", False) + with pytest.raises(ImportError, match="tkinter not available.*cannot create GUI"): + guitools.asksaveasfilename() + + +def test_multicolumnlistbox_no_tkinter(monkeypatch): + monkeypatch.setattr(guitools, "HAVE_TKINTER", False) + headers = ["First", "Middle", "Last"] + lst1 = ["Tony", "Jennifer", "Albert", "Marion"] + lst2 = ["J.", "M.", "E.", "K."] + lst3 = ["Anderson", "Smith", "Kingsley", "Cotter"] + with pytest.raises(ImportError, match="tkinter not available.*cannot create GUI"): + guitools.MultiColumnListbox("Select person", headers, [lst1, lst2, lst3]) diff --git a/pyyeti/tests/test_n2p.py b/pyyeti/tests/test_n2p.py index 3345e19..3925f1b 100644 --- a/pyyeti/tests/test_n2p.py +++ b/pyyeti/tests/test_n2p.py @@ -5,6 +5,7 @@ from scipy.io import matlab import io import os +import re from pyyeti import nastran, cb from pyyeti.nastran import n2p, op2, op4 import pytest @@ -1948,7 +1949,9 @@ def test_badrbe3_warn(): uset = n2p.addgrid(None, np.arange(1, n + 1), "b", 0, np.column_stack((x, y, z)), 0) uset = n2p.addgrid(uset, 100, "b", 0, [5, 5, 5], 0) with pytest.warns(RuntimeWarning, match="matrix is poorly conditioned"): - rbe3 = n2p.formrbe3(uset, 100, 123456, [123, [1, 2, 3, 4, 5]]) + regex = re.compile(r"ill\-conditioned matrix", re.IGNORECASE) + with pytest.warns(la.LinAlgWarning, match=regex): + rbe3 = n2p.formrbe3(uset, 100, 123456, [123, [1, 2, 3, 4, 5]]) def test_rbe3_badum():