From a8415d90fac1126ad54c52654e2b45dfce497c6c Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 12 Feb 2025 15:15:06 -0500 Subject: [PATCH] Update CI workflows and package requirements (#419) * Update testing and requirements for Python, numpy, and scipy. * Update matplotlib to >=3.8 * Update versions in gh workflows * Also limit python version for linkcheck * Update type annotations following deprecations in python 3.9 * Update change log. * py313 test should use numpy 2.1; Fix tests for numpy 2 * Remove E203 from pycodestyle It conflicts with https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements * Just use one pep8speaks config * More explicit code branching based on python version. --- .github/workflows/ci_cron_weekly.yml | 4 ++-- .github/workflows/ci_tests.yml | 9 +++++---- .pep8speaks.yml | 11 +++++++++++ CHANGES.rst | 6 ++++++ docs/sbpy/time.rst | 2 +- pep8speaks.yml | 13 ------------- sbpy/activity/gas/core.py | 6 ++---- sbpy/dynamics/state.py | 24 ++++++++++++------------ sbpy/dynamics/syndynes.py | 20 ++++++++++---------- sbpy/dynamics/tests/test_models.py | 3 --- sbpy/imageanalysis/utils.py | 18 +++++++++--------- sbpy/time.py | 2 +- setup.cfg | 12 ++++++------ tox.ini | 20 +++++++++----------- 14 files changed, 74 insertions(+), 76 deletions(-) delete mode 100644 pep8speaks.yml diff --git a/.github/workflows/ci_cron_weekly.yml b/.github/workflows/ci_cron_weekly.yml index 99d52da2..f705f708 100644 --- a/.github/workflows/ci_cron_weekly.yml +++ b/.github/workflows/ci_cron_weekly.yml @@ -39,8 +39,8 @@ jobs: - name: Link check linux: linkcheck - - name: Python 3.11 with dev versions of key dependencies - linux: py311-test-devdeps-cov + - name: Python 3.13 with dev versions of key dependencies + linux: py313-test-devdeps-cov posargs: --verbose allowed_failures: diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 90b0c93b..9904a7a6 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -3,6 +3,7 @@ # This file performs testing using tox and tox.ini to define and configure the test environments. # Remove -numpy126 when we support testing with numpy 2.0 +# Allow testing of optional dependencies on Python 3.12+ when pyoorb is updated. name: CI Tests @@ -45,12 +46,12 @@ jobs: - name: Code style checks linux: codestyle - - name: Python 3.11 with minimal dependencies, measuring coverage - linux: py311-test-numpy126-cov + - name: Python 3.13 with minimal dependencies, measuring coverage + linux: py313-test-cov coverage: codecov - - name: Python 3.10 with all optional dependencies, measuring coverage - linux: py310-test-numpy126-alldeps-cov + - name: Python 3.11 with all optional dependencies, measuring coverage + linux: py311-test-numpy126-alldeps-cov coverage: codecov - name: Python 3.9 with oldest supported versions diff --git a/.pep8speaks.yml b/.pep8speaks.yml index 9416eea9..5fafe481 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -1,2 +1,13 @@ pycodestyle: max-line-length: 88 + + ignore: + - E741 # single-letter small-caps + - E226 # Don't force "missing whitespace around arithmetic operator" + - E402 # .conf has to be set in the __init__.py modules imports + - E501 # line too long + - E203 # whitespace before colon + + exclude: + - _astropy_init.py + - version.py diff --git a/CHANGES.rst b/CHANGES.rst index 51bed11b..7a3ef90c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ 0.6.0 (unreleased) ================== +Update supported versions [#419]: +- Python >= 3.9 +- numpy >= 1.26 +- scipy >= 1.10 + + New Features ------------ diff --git a/docs/sbpy/time.rst b/docs/sbpy/time.rst index 0ca92a87..c41b514f 100644 --- a/docs/sbpy/time.rst +++ b/docs/sbpy/time.rst @@ -22,7 +22,7 @@ Or, use the ``et`` property to transform dates to ephemeris time: .. doctest:: - >>> Time("2010-11-04 13:59:47.31", scale="utc").et + >>> print(Time("2010-11-04 13:59:47.31", scale="utc").et) # doctest: +FLOAT_CMP 342151253.4925505 The conversion from UTC is thought to be good to the 0.1 ms level. See the `sbpy` tests for more information. diff --git a/pep8speaks.yml b/pep8speaks.yml deleted file mode 100644 index 72ff66d0..00000000 --- a/pep8speaks.yml +++ /dev/null @@ -1,13 +0,0 @@ -pycodestyle: - max-line-length: 120 # Default is 79 in PEP8 - - ignore: - - E741 # single-letter small-caps - - E226 # Don't force "missing whitespace around arithmetic operator" - - E402 # .conf has to be set in the __init__.py modules imports - - E501 # line too long - - - exclude: - - _astropy_init.py - - version.py \ No newline at end of file diff --git a/sbpy/activity/gas/core.py b/sbpy/activity/gas/core.py index 888ece67..7a4ed92e 100644 --- a/sbpy/activity/gas/core.py +++ b/sbpy/activity/gas/core.py @@ -13,10 +13,8 @@ from abc import ABC, abstractmethod -# distutils is deprecated in python 3.10 and will be removed in 3.12 (PEP 632). -# Migration from distutils.log -> logging from dataclasses import dataclass -from typing import Callable, Tuple +from typing import Callable import numpy as np import astropy.units as u @@ -1354,7 +1352,7 @@ def _make_radial_logspace_grid(self) -> np.ndarray: def _outflow_axis_sampling( self, x: np.float64, y: np.float64, theta: np.float64 - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """Construct a list of points along the outflow axis, sampled to be more dense around the minimum distance to (x, y). diff --git a/sbpy/dynamics/state.py b/sbpy/dynamics/state.py index 770f0c27..98cd6f29 100644 --- a/sbpy/dynamics/state.py +++ b/sbpy/dynamics/state.py @@ -16,14 +16,14 @@ ] import abc -from typing import Iterable, List, Optional, Tuple, TypeVar, Union +import sys +from typing import Iterable, Optional, TypeVar, Union from packaging.version import Version -try: - # python 3.11 feature - from typing import Self -except ImportError: +if sys.version_info[:2] < (3, 11): Self = TypeVar("Self", bound="StateBase") +else: + from typing import Self import numpy as np import astropy @@ -204,7 +204,7 @@ def __neg__(self) -> Self: frame=self.frame, ) - def __abs__(self) -> Tuple[u.Quantity, u.Quantity]: + def __abs__(self) -> tuple[u.Quantity, u.Quantity]: """Return the magnitude of the position and velocity.""" r = np.sqrt(np.sum(self.r**2, axis=-1)) v = np.sqrt(np.sum(self.v**2, axis=-1)) @@ -347,11 +347,11 @@ def from_states(cls, states: Iterable[Self]) -> Self: """ frame: BaseCoordinateFrame = states[0].frame - states_: List[State] = [state.transform_to(frame) for state in states] + states_: list[State] = [state.transform_to(frame) for state in states] - r: List[u.Quantity] = [state.r for state in states_] - v: List[u.Quantity] = [state.v for state in states_] - t: List[Union[u.Quantity, Time]] = [state.t for state in states_] + r: list[u.Quantity] = [state.r for state in states_] + v: list[u.Quantity] = [state.v for state in states_] + t: list[Union[u.Quantity, Time]] = [state.t for state in states_] return State(r, v, t, frame=frame) @@ -399,8 +399,8 @@ def from_ephem( """ - rectangular: Tuple[str] = ("x", "y", "z", "vx", "vy", "vz", "date") - spherical: Tuple[str] = ( + rectangular: tuple[str] = ("x", "y", "z", "vx", "vy", "vz", "date") + spherical: tuple[str] = ( "ra", "dec", "Delta", diff --git a/sbpy/dynamics/syndynes.py b/sbpy/dynamics/syndynes.py index 4130dbcf..effed406 100644 --- a/sbpy/dynamics/syndynes.py +++ b/sbpy/dynamics/syndynes.py @@ -18,15 +18,15 @@ ] import abc +import sys import time import logging -from typing import Iterable, List, Tuple, Union, Optional, TypeVar +from typing import Iterable, Union, Optional, TypeVar -try: - # python 3.11 feature +if sys.version_info[:2] < (3, 11): + Self = TypeVar("Self", bound="StateBase") +else: from typing import Self -except ImportError: - Self = TypeVar("Self", bound="SynGenerator") import numpy as np import astropy.units as u @@ -327,7 +327,7 @@ def to_ephem(self) -> Ephem: if len(self) == 0: return Ephem() - tables: List[Ephem] = [s.to_ephem().table for s in self] + tables: list[Ephem] = [s.to_ephem().table for s in self] return Ephem.from_table( vstack(tables, metadata_conflicts="error"), @@ -477,7 +477,7 @@ def initialize_states(self) -> None: """ - states: List[State] = [] + states: list[State] = [] for age in self.ages: t_i: Time = self.source.t - age state = self.solver.solve(self.source, t_i, 0) @@ -497,7 +497,7 @@ def solve(self) -> None: logger: logging.Logger = logging.getLogger() - particles: List[State] = [] + particles: list[State] = [] t0: float = time.monotonic() for i in range(self.betas.size): for j in range(self.ages.size): @@ -603,7 +603,7 @@ def synchrones(self) -> Synchrones: return Synchrones([self.synchrone(i) for i in range(len(self.ages))]) - def source_orbit(self, dt: u.Quantity) -> Union[State, Tuple[State, SkyCoord]]: + def source_orbit(self, dt: u.Quantity) -> Union[State, tuple[State, SkyCoord]]: """Calculate and observe the orbit of the dust source. @@ -627,7 +627,7 @@ def source_orbit(self, dt: u.Quantity) -> Union[State, Tuple[State, SkyCoord]]: """ - states: List[State] = [] + states: list[State] = [] for i in range(len(dt)): t: Time = self.source.t + dt[i] states.append(self.solver.solve(self.source, t, 0)) diff --git a/sbpy/dynamics/tests/test_models.py b/sbpy/dynamics/tests/test_models.py index fa3baee6..21d8b69a 100644 --- a/sbpy/dynamics/tests/test_models.py +++ b/sbpy/dynamics/tests/test_models.py @@ -159,9 +159,6 @@ def test_GM(self): solver = SolarGravity() assert u.isclose(solver.GM, const.G * const.M_sun, rtol=1e-12) - @pytest.mark.skipif( - "scipy_version[0] < 2 and scipy_version[1] < 8", reason="requires scipy>=1.10" - ) def test_solverfailed(self): r = [0, 1, 0] * u.au v = [0, -1, 1] * u.km / u.s diff --git a/sbpy/imageanalysis/utils.py b/sbpy/imageanalysis/utils.py index 89e462b3..41803e72 100644 --- a/sbpy/imageanalysis/utils.py +++ b/sbpy/imageanalysis/utils.py @@ -32,10 +32,10 @@ def rarray(shape, yx=None, subsample=0): ------- >>> from sbpy.imageanalysis.utils import rarray >>> r = rarray((5, 5)) - >>> r[2, 2] # docest: +FLOAT_CMP + >>> print(r[2, 2]) # docest: +FLOAT_CMP 0.0 >>> r = rarray((5, 5), yx=(0, 0)) - >>> r[2, 2] # docest: +FLOAT_CMP + >>> print(r[2, 2]) # docest: +FLOAT_CMP 2.8284271247461903 """ @@ -89,10 +89,10 @@ def rebin(a, factor, flux=False, trim=False): ------- >>> from sbpy.imageanalysis.utils import xarray, rebin >>> x = xarray((10, 10)) - >>> x[0, :2].mean() # docest: +FLOAT_CMP + >>> print(x[0, :2].mean()) # docest: +FLOAT_CMP 0.5 >>> x2 = rebin(x, -2) - >>> x2[0, 0] # docest: +FLOAT_CMP + >>> print(x2[0, 0]) # docest: +FLOAT_CMP 0.5 """ @@ -102,7 +102,7 @@ def rebin(a, factor, flux=False, trim=False): def mini(a, factor): b = a[::-factor] for i in range(-factor - 1): - b += a[(i + 1)::-factor] + b += a[(i + 1) :: -factor] if not flux: b = b / -factor return b @@ -176,10 +176,10 @@ def refine_pixel(func, subsample, yx_pixel, yx, **kwargs): >>> from sbpy.imageanalysis.utils import rarray, refine_pixel >>> yx = (2, 2) # the center of the radial array >>> r = rarray((5, 5), yx=yx) - >>> r[2, 2] # docest: +FLOAT_CMP + >>> print(r[2, 2]) # docest: +FLOAT_CMP 0.0 >>> f = refine_pixel(rarray, 10, (2, 2), yx) - >>> np.isclose(f, 0.03826, rtol=0.01, atol=0.01) + >>> print(np.isclose(f, 0.03826, rtol=0.01, atol=0.01)) True """ @@ -212,7 +212,7 @@ def xarray(shape, yx=[0, 0], th=None): -------- >>> from sbpy.imageanalysis.utils import xarray >>> x = xarray((10, 10)) - >>> x[0, 3] + >>> print(x[0, 3]) 3 """ @@ -250,7 +250,7 @@ def yarray(shape, yx=[0, 0], th=None): >>> from sbpy.imageanalysis.utils import yarray >>> y = yarray((10, 10)) - >>> y[3, 0] + >>> print(y[3, 0]) 3 """ diff --git a/sbpy/time.py b/sbpy/time.py index 31f13f4e..bd46a5c7 100644 --- a/sbpy/time.py +++ b/sbpy/time.py @@ -26,7 +26,7 @@ class SpiceEphemerisTime(TimeFromEpoch): >>> import sbpy.time >>> >>> t = Time("2023-11-21") - >>> print(t.et) + >>> print(float(t.et)) # doctest: +FLOAT_CMP 753796869.1828325 >>> t = Time(0, format="et") diff --git a/setup.cfg b/setup.cfg index cea01f96..c657dcbd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,12 +25,12 @@ classifiers = [options] packages = find: zip_save = False -python_requires = >=3.8 +python_requires = >=3.9 setup_requires = setuptools_scm # keep requirements in synchronization with docs/install.rst install_requires = - numpy>=1.21.0 + numpy>=1.24.0 astropy>=5.3.3 include_package_data = True @@ -38,7 +38,7 @@ include_package_data = True recommended = ads>=0.12 astroquery>=0.4.5 - scipy>=1.6 + scipy>=1.10 synphot>=1.1.1 all = ads>=0.12 @@ -47,7 +47,7 @@ all = photutils pyoorb pyyaml - scipy>=1.6 + scipy>=1.10 synphot>=1.1.1 test = pytest>=7.0 @@ -59,10 +59,10 @@ test = coverage docs = sbpy[all,test] - matplotlib>=3.3 + matplotlib>=3.8 sphinx-astropy>=1.3 astropy!=6.1.* - scipy>=1.6 + scipy>=1.10 numpy<2 [options.package_data] diff --git a/tox.ini b/tox.ini index 3283f2b8..ce4b14cf 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{38,39,310,311,dev}-test{,-alldeps,-oldestdeps,-devdeps,-numpy121,-numpy122,-numpy123,-numpy124,-numpy125,-numpy126,numpy-20}{,-cov} + py{311,312,313,dev}-test{,-alldeps,-oldestdeps,-devdeps,-numpy124,-numpy125,-numpy126,numpy-20,numpy-21}{,-cov} build_docs linkcheck codestyle @@ -39,33 +39,29 @@ description = devdeps: with the latest developer version of key dependencies oldestdeps: with the oldest supported version of key dependencies cov: and test coverage - numpy121: with numpy 1.21.* - numpy122: with numpy 1.22.* - numpy123: with numpy 1.23.* numpy124: with numpy 1.24.* numpy125: with numpy 1.25.* numpy126: with numpy 1.26.* numpy20: with numpy 2.0.* + numpy21: with numpy 2.1.* image: with image tests mpldev: with the latest developer version of matplotlib double: twice in a row to check for global state changes deps = - numpy121: numpy==1.21.* - numpy122: numpy==1.22.* - numpy123: numpy==1.23.* numpy124: numpy==1.24.* numpy125: numpy==1.25.* numpy126: numpy==1.26.* numpy20: numpy==2.0.* + numpy21: numpy==2.1.* image: pytest-mpl # The oldestdeps factor is intended to be used to install the oldest versions of all # dependencies that have a minimum version. - oldestdeps: numpy==1.21.* - oldestdeps: matplotlib==3.3.* - oldestdeps: scipy==1.6.* + oldestdeps: numpy==1.24.* + oldestdeps: matplotlib==3.8.* + oldestdeps: scipy==1.10.* oldestdeps: synphot==1.1.* oldestdeps: astropy==5.3.* oldestdeps: ads==0.12.* @@ -91,6 +87,7 @@ commands = cov: coverage xml -o {toxinidir}/coverage.xml [testenv:build_docs] +basepython = py311 changedir = docs description = invoke sphinx-build to build the HTML docs extras = docs @@ -99,6 +96,7 @@ commands = sphinx-build -W -b html . _build/html {posargs} [testenv:linkcheck] +basepython = py311 changedir = docs description = check the links in the HTML docs extras = docs @@ -138,4 +136,4 @@ skip_install = true description = check code style, e.g. with flake8 deps = flake8 # commands = flake8 sbpy --count --select=E101,W191,W291,W292,W293,W391,E111,E112,E113,E30,E502,E722,E901,E902,E999,F822,F823 -commands = flake8 sbpy --count --select=E101,E111,E112,E113,E124,E201,E202,E203,E211,E221,E225,E231,E241,E251,E261,E265,E271,E272,E301,E302,E303,E305,E502,E703,E711,E712,E714,E722,E901,E902,W191,W291,W292,W293,W391 +commands = flake8 sbpy --count --select=E101,E111,E112,E113,E124,E201,E202,E211,E221,E225,E231,E241,E251,E261,E265,E271,E272,E301,E302,E303,E305,E502,E703,E711,E712,E714,E722,E901,E902,W191,W291,W292,W293,W391