Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-sunpy for openSUSE:Factory checked in at 2025-08-03 13:38:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sunpy (Old) and /work/SRC/openSUSE:Factory/.python-sunpy.new.1085 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sunpy" Sun Aug 3 13:38:05 2025 rev:37 rq:1297171 version:7.0.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sunpy/python-sunpy.changes 2025-07-21 20:01:41.290551096 +0200 +++ /work/SRC/openSUSE:Factory/.python-sunpy.new.1085/python-sunpy.changes 2025-08-03 13:38:44.794775433 +0200 @@ -1,0 +2,14 @@ +Sat Aug 2 17:19:32 UTC 2025 - Ben Greiner <c...@bnavigator.de> + +- Update to 7.0.1 + * Fixed a bug where the time format 2001-02-03T04:05:06Z was + being parsed through different code than 2001-02-03T04:05:06 or + 2001-02-03T04:05:06.0Z. (#8265) + * Fixed a bug where sunpy.util.system_info would report sunpy as + an optional dependency of itself instead of properly reporting + all of the optional dependencies. (#8294) + * Fixed sunpy.util.system_info so that the version reported for a + development installation of sunpy itself or of a dependency is + accurate. (#8297) + +------------------------------------------------------------------- Old: ---- sunpy-7.0.0.tar.gz New: ---- sunpy-7.0.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sunpy.spec ++++++ --- /var/tmp/diff_new_pack.ehX33a/_old 2025-08-03 13:38:45.498804629 +0200 +++ /var/tmp/diff_new_pack.ehX33a/_new 2025-08-03 13:38:45.498804629 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-sunpy -Version: 7.0.0 +Version: 7.0.1 Release: 0 Summary: SunPy core package: Python for Solar Physics License: Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND MIT @@ -49,7 +49,7 @@ Requires: python-parfive >= 2.1.0 Requires: python-pyerfa >= 2.0.1.1 Requires: python-requests >= 2.32.0 -# pafived[ftp], ignore rpmlint's python-leftover-require +# parfive[ftp], ignore rpmlint's python-leftover-require Requires: python-aioftp >= 0.17.1 # SECTION project.optional-dependencies:asdf Recommends: python-asdf >= 3 ++++++ sunpy-7.0.0.tar.gz -> sunpy-7.0.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/.cruft.json new/sunpy-7.0.1/.cruft.json --- old/sunpy-7.0.0/.cruft.json 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/.cruft.json 2025-07-31 16:11:53.000000000 +0200 @@ -1,6 +1,6 @@ { "template": "https://github.com/sunpy/package-template", - "commit": "15fdf534198e4f67f0a667af7d2367e93de181c5", + "commit": "88a0a31eadf2be84895e32ffd792768c2863f7ca", "checkout": null, "context": { "cookiecutter": { @@ -32,7 +32,7 @@ ".github/workflows/sub_package_update.yml" ], "_template": "https://github.com/sunpy/package-template", - "_commit": "15fdf534198e4f67f0a667af7d2367e93de181c5" + "_commit": "88a0a31eadf2be84895e32ffd792768c2863f7ca" } }, "directory": null diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/CHANGELOG.rst new/sunpy-7.0.1/CHANGELOG.rst --- old/sunpy-7.0.0/CHANGELOG.rst 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/CHANGELOG.rst 2025-07-31 16:11:53.000000000 +0200 @@ -1,3 +1,20 @@ +7.0.1 (2025-07-31) +================== + +Bug Fixes +--------- + +- Fixed a bug where the time format ``2001-02-03T04:05:06Z`` was being parsed through different code than ``2001-02-03T04:05:06`` or ``2001-02-03T04:05:06.0Z``. (`#8265 <https://github.com/sunpy/sunpy/pull/8265>`__) +- Fixed a bug where :func:`sunpy.util.system_info` would report `sunpy` as an optional dependency of itself instead of properly reporting all of the optional dependencies. (`#8294 <https://github.com/sunpy/sunpy/pull/8294>`__) +- Fixed :func:`sunpy.util.system_info` so that the version reported for a development installation of `sunpy` itself or of a dependency is accurate. (`#8297 <https://github.com/sunpy/sunpy/pull/8297>`__) + + +Documentation +------------- + +- Fixed errors and added elaborations to the docstring for `~sunpy.coordinates.Helioprojective`. (`#8293 <https://github.com/sunpy/sunpy/pull/8293>`__) + + 7.0.0 (2025-06-18) ================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/PKG-INFO new/sunpy-7.0.1/PKG-INFO --- old/sunpy-7.0.0/PKG-INFO 2025-06-18 17:14:50.476620200 +0200 +++ new/sunpy-7.0.1/PKG-INFO 2025-07-31 16:12:05.466531300 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: sunpy -Version: 7.0.0 +Version: 7.0.1 Summary: SunPy core package: Python for Solar Physics Author-email: The SunPy Community <su...@googlegroups.com> License: Copyright (c) 2013-2025 The SunPy developers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/_typos.toml new/sunpy-7.0.1/_typos.toml --- old/sunpy-7.0.0/_typos.toml 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/_typos.toml 2025-07-31 16:11:53.000000000 +0200 @@ -25,4 +25,5 @@ "pn", "lightyear", "PNGs", + "setp", ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/_version.py new/sunpy-7.0.1/sunpy/_version.py --- old/sunpy-7.0.0/sunpy/_version.py 2025-06-18 17:14:49.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/_version.py 2025-07-31 16:12:05.000000000 +0200 @@ -17,5 +17,5 @@ __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = '7.0.0' -__version_tuple__ = version_tuple = (7, 0, 0) +__version__ = version = '7.0.1' +__version_tuple__ = version_tuple = (7, 0, 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/coordinates/frames.py new/sunpy-7.0.1/sunpy/coordinates/frames.py --- old/sunpy-7.0.0/sunpy/coordinates/frames.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/coordinates/frames.py 2025-07-31 16:11:53.000000000 +0200 @@ -440,18 +440,35 @@ @add_common_docstring(**_frame_parameters()) class Helioprojective(SunPyBaseCoordinateFrame): """ - A coordinate or frame in the Helioprojective Cartesian (HPC) system, which is observer-based. + A coordinate or frame in the Helioprojective Cartesian (HPC) system. - - The origin is the location of the observer. - - ``Tx`` (aka "theta_x") is the angle relative to the plane containing the Sun-observer line - and the Sun's rotation axis, with positive values in the direction of the Sun's west limb. - - ``Ty`` (aka "theta_y") is the angle relative to the Sun's equatorial plane, with positive - values in the direction of the Sun's north pole. - - ``distance`` is the Sun-observer distance. - - This system is frequently used in a projective form without ``distance`` specified. For - observations looking very close to the center of the Sun, where the small-angle approximation - is appropriate, ``Tx`` and ``Ty`` can be approximated as Cartesian components. + This is an observer-based spherical coordinate system, with: + + - The origin is the observer location. + - The line connecting the poles of the frame is parallel to the component of the Sun's + rotation axis that is perpendicular to the observer-Sun line. + - ``Tx`` (short for "theta_x", or :math:`\\theta_x`) is the longitude, the angle relative to + the plane containing the observer-Sun line and the poles of the frame, with positive values + in the direction of the Sun's west limb. + - ``Ty`` (short for "theta_y", or :math:`\\theta_y`) is the latitude, the angle relative to the + plane that is perpendicular to the poles of the frame, with positive values in the direction + of the Sun's north pole. + - ``distance`` is the observer-object distance. + + .. note:: + It can be confusing that the name of this coordinate system uses the adjective "Cartesian" + despite being a spherical coordinate system. The reason for this name is because close to + the center of the Sun where the small-angle approximation is appropriate, ``Tx`` and ``Ty`` + can be thought of as two components of a Cartesian-like coordinate system. The corresponding + third Cartesian-like component, sometimes called zeta (:math:`\\zeta`), is defined to be the + Sun-observer distance (``observer.radius``) minus the observer-object distance (``distance``). + + This system is frequently used in a projective form without ``distance`` specified. When + transforming such a 2D coordinate to another frame, the object location usually needs to be + fully 3D, which is achieved by calling :meth:`.make_3d` to generate the ``distance`` component. + The default assumption is that the object lies on the surface of the Sun if the 2D coordinate is + on the solar disk, but is otherwise undefined if the 2D coordinate is beyond the solar limb. + This assumption can be modified using a screen (e.g., :func:`~sunpy.coordinates.SphericalScreen`). A new instance can be created using the following signatures (note that if supplied, ``obstime`` and ``observer`` must be keyword arguments):: @@ -463,16 +480,20 @@ ---------- {data} Tx : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` - The theta_x coordinate for this object. Not needed if ``data`` is given. + The theta_x (:math:`\\theta_x`) component for this object. Not needed if ``data`` is given. Ty : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` - The theta_y coordinate for this object. Not needed if ``data`` is given. + The theta_y (:math:`\\theta_y`) component for this object. Not needed if ``data`` is given. distance : `~astropy.units.Quantity` - The distance coordinate from the observer for this object. - Not needed if ``data`` is given. + The distance component from the observer for this object. Not needed if ``data`` is given. {observer} {rsun} {common} + See Also + -------- + HelioprojectiveRadial + ~sunpy.coordinates.PlanarScreen, ~sunpy.coordinates.SphericalScreen + Examples -------- >>> from astropy.coordinates import SkyCoord diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/map/mapbase.py new/sunpy-7.0.1/sunpy/map/mapbase.py --- old/sunpy-7.0.0/sunpy/map/mapbase.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/map/mapbase.py 2025-07-31 16:11:53.000000000 +0200 @@ -3004,7 +3004,7 @@ """ Returns coordinates of the contours for a given level value. - For details of the contouring algorithm, see :func:`contourpy.contour_generator` or :func:`contourpy.contour_generator`. + For details of the contouring algorithm, see :func:`contourpy.contour_generator` or :func:`skimage.measure.find_contours`. Parameters ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/map/tests/test_map_factory.py new/sunpy-7.0.1/sunpy/map/tests/test_map_factory.py --- old/sunpy-7.0.0/sunpy/map/tests/test_map_factory.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/map/tests/test_map_factory.py 2025-07-31 16:11:53.000000000 +0200 @@ -288,7 +288,7 @@ """ with pytest.warns(SunpyUserWarning, match='Failed to read'): amap = sunpy.map.Map('s3://data.sunpy.org/aiapy', fsspec_kwargs={'anon':True}, allow_errors=True) - assert all(isinstance(am, sunpy.map.GenericMap) for am in amap) + assert all(isinstance(am, sunpy.map.GenericMap) for am in amap) def test_save(): @@ -396,16 +396,16 @@ files = [AIA_171_IMAGE, get_test_filepath('not_actually_fits.fits')] with pytest.warns(SunpyUserWarning, match='Failed to read'): amap = sunpy.map.Map(files, allow_errors=True) - assert amap.data.shape == (128, 128) + assert amap.data.shape == (128, 128) files = [AIA_171_IMAGE, get_test_filepath('not_actually_fits.fits'), AIA_171_IMAGE] with pytest.warns(SunpyUserWarning, match='Failed to read'): amap = sunpy.map.Map(files, allow_errors=True) - assert len(amap) == 2 + assert len(amap) == 2 with pytest.warns(SunpyUserWarning, match='Failed to read'): amap = sunpy.map.Map(files, allow_errors=True, sequence=True) - assert len(amap) == 2 + assert len(amap) == 2 with pytest.raises(OSError, match='Failed to read'): sunpy.map.Map(files, allow_errors=False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/map/tests/test_mapbase.py new/sunpy-7.0.1/sunpy/map/tests/test_mapbase.py --- old/sunpy-7.0.0/sunpy/map/tests/test_mapbase.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/map/tests/test_mapbase.py 2025-07-31 16:11:53.000000000 +0200 @@ -1229,28 +1229,28 @@ def test_validate_meta(generic_map): """Check to see if_validate_meta displays an appropriate error""" + bad_header = { + 'CRVAL1': 0, + 'CRVAL2': 0, + 'CRPIX1': 5, + 'CRPIX2': 5, + 'CDELT1': 10, + 'CDELT2': 10, + 'CUNIT1': 'ARCSEC', + 'CUNIT2': 'ARCSEC', + 'PC1_1': 0, + 'PC1_2': -1, + 'PC2_1': 1, + 'PC2_2': 0, + 'NAXIS1': 6, + 'NAXIS2': 6, + 'date-obs': '1970/01/01T00:00:00', + 'obsrvtry': 'Foo', + 'detector': 'bar', + 'wavelnth': 10, + 'waveunit': 'ANGSTROM' + } with pytest.warns(SunpyMetadataWarning) as w: - bad_header = { - 'CRVAL1': 0, - 'CRVAL2': 0, - 'CRPIX1': 5, - 'CRPIX2': 5, - 'CDELT1': 10, - 'CDELT2': 10, - 'CUNIT1': 'ARCSEC', - 'CUNIT2': 'ARCSEC', - 'PC1_1': 0, - 'PC1_2': -1, - 'PC2_1': 1, - 'PC2_2': 0, - 'NAXIS1': 6, - 'NAXIS2': 6, - 'date-obs': '1970/01/01T00:00:00', - 'obsrvtry': 'Foo', - 'detector': 'bar', - 'wavelnth': 10, - 'waveunit': 'ANGSTROM' - } sunpy.map.Map((generic_map.data, bad_header)) assert 'waveunit'.upper() in str(w[0].message) @@ -1310,13 +1310,13 @@ def test_missing_metadata_warnings(): # Checks that warnings for missing metadata are only raised once - with pytest.warns(Warning) as record: - header = { - 'cunit1': 'arcsec', - 'cunit2': 'arcsec', - 'ctype1': 'HPLN-TAN', - 'ctype2': 'HPLT-TAN', - } + header = { + 'cunit1': 'arcsec', + 'cunit2': 'arcsec', + 'ctype1': 'HPLN-TAN', + 'ctype2': 'HPLT-TAN', + } + with pytest.warns(Warning) as record: # NOQA: PT030,PT031 array_map = sunpy.map.Map(np.random.rand(20, 15), header) array_map.peek() # There should be 2 warnings for missing metadata (obstime and observer location) @@ -1795,7 +1795,7 @@ check_arithmetic_value_and_units(new_map, value * aia171_test_map.quantity) new_map = aia171_test_map / value check_arithmetic_value_and_units(new_map, aia171_test_map.quantity / value) - with pytest.warns(RuntimeWarning, match='divide by zero encountered in'): + with pytest.warns(RuntimeWarning, match='divide by zero encountered in'): # NOQA: PT031 new_map = value / aia171_test_map check_arithmetic_value_and_units(new_map, value / aia171_test_map.quantity) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/map/tests/test_mapbase_dask.py new/sunpy-7.0.1/sunpy/map/tests/test_mapbase_dask.py --- old/sunpy-7.0.0/sunpy/map/tests/test_mapbase_dask.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/map/tests/test_mapbase_dask.py 2025-07-31 16:11:53.000000000 +0200 @@ -31,7 +31,7 @@ # This is needed for the reproject_to function -with pytest.warns(VerifyWarning, match="Invalid 'BLANK' keyword in header."): +with pytest.warns(VerifyWarning, match="Invalid 'BLANK' keyword in header."): # NOQA: PT031 with fits.open(get_test_filepath('aia_171_level1.fits')) as hdu: with pytest.warns(FITSFixedWarning, match="'datfix' made the change"): aia_wcs = astropy.wcs.WCS(header=hdu[0].header) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/map/tests/test_reproject_to.py new/sunpy-7.0.1/sunpy/map/tests/test_reproject_to.py --- old/sunpy-7.0.0/sunpy/map/tests/test_reproject_to.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/map/tests/test_reproject_to.py 2025-07-31 16:11:53.000000000 +0200 @@ -126,10 +126,9 @@ def test_rsun_mismatch_warning(aia171_test_map, hpc_header): + # Modifying the `hpc_header` rsun value to create a mismatch + hpc_header["rsun_ref"] += 1 with pytest.warns(SunpyUserWarning, match="rsun mismatch detected: "): - # Modifying the `hpc_header` rsun value to create a mismatch - hpc_header["rsun_ref"] += 1 - # Reproject with the mismatched rsun aia171_test_map.reproject_to(hpc_header) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/net/tests/test_scraper.py new/sunpy-7.0.1/sunpy/net/tests/test_scraper.py --- old/sunpy-7.0.0/sunpy/net/tests/test_scraper.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/net/tests/test_scraper.py 2025-07-31 16:11:53.000000000 +0200 @@ -16,9 +16,9 @@ def test_directory_date_pattern(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y/%m/%d/%Y%m%d_%H%M%S_59.fit.gz') - testpath = '2014/03/05/20140305_013000_59.fit.gz' - d = parse_time((2014, 3, 5, 1, 30)) - assert s.matches(testpath, d) + testpath = '2014/03/05/20140305_013000_59.fit.gz' + d = parse_time((2014, 3, 5, 1, 30)) + assert s.matches(testpath, d) def test_directory_date_pattern_new_format(): @@ -31,9 +31,9 @@ def test_directory_date_pattern_false(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y/%m/%d/%Y%m%d_%H%M%S_59.fit.gz') - testpath = '2013/03/05/20140305_013000_59.fit.gz' - d = parse_time((2014, 3, 5, 1, 30)) - assert not s.matches(testpath, d) + testpath = '2013/03/05/20140305_013000_59.fit.gz' + d = parse_time((2014, 3, 5, 1, 30)) + assert not s.matches(testpath, d) def test_directory_date_patternFalse_new_format(): @@ -46,9 +46,9 @@ def test_directory_obs_pattern(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%y%m%d/{observatory}_%Y%m%d.fits', observatory='SDO') - testpath = '140305/SDO_20140305.fits' - d = parse_time((2014, 3, 5)) - assert s.matches(testpath, d) + testpath = '140305/SDO_20140305.fits' + d = parse_time((2014, 3, 5)) + assert s.matches(testpath, d) def test_directory_obs_pattern_new_format(): @@ -61,10 +61,10 @@ def test_directory_range(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y/%m/%d/%Y%m%d_%H.fit.gz') - directory_list = ['2009/12/30/', '2009/12/31/', '2010/01/01/', - '2010/01/02/', '2010/01/03/'] - timerange = TimeRange('2009-12-30', '2010-01-03') - assert s.range(timerange) == directory_list + directory_list = ['2009/12/30/', '2009/12/31/', '2010/01/01/', + '2010/01/02/', '2010/01/03/'] + timerange = TimeRange('2009-12-30', '2010-01-03') + assert s.range(timerange) == directory_list def test_directory_range_new_format(): @@ -79,9 +79,9 @@ with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): # Test for Windows where '\' is a path separator and not part of the regex s = Scraper('scheme://a.url.with/a/few/forward/slashes/andbacklash\\inthename.ext', regex=True) - timerange = TimeRange('2019-02-01', '2019-02-03') - directory = s.range(timerange) - assert directory == ['scheme://a.url.with/a/few/forward/slashes/'] + timerange = TimeRange('2019-02-01', '2019-02-03') + directory = s.range(timerange) + assert directory == ['scheme://a.url.with/a/few/forward/slashes/'] def test_directory_regex_new_format(): @@ -95,10 +95,10 @@ def test_directory_rangeFalse(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y%m%d/%Y%m%d_%H.fit.gz') - directory_list = ['20091230/', '20091231/', '20100101/', - '20090102/', '20090103/'] - timerange = TimeRange('2009/12/30', '2010/01/03') - assert s.range(timerange) != directory_list + directory_list = ['20091230/', '20091231/', '20100101/', + '20090102/', '20090103/'] + timerange = TimeRange('2009/12/30', '2010/01/03') + assert s.range(timerange) != directory_list def test_directory_range_false_new_format(): @@ -112,9 +112,9 @@ def test_no_date_directory(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('mySpacecraft/myInstrument/xMinutes/aaa%y%b.ext') - directory_list = ['mySpacecraft/myInstrument/xMinutes/'] - timerange = TimeRange('2009/11/20', '2010/01/03') - assert s.range(timerange) == directory_list + directory_list = ['mySpacecraft/myInstrument/xMinutes/'] + timerange = TimeRange('2009/11/20', '2010/01/03') + assert s.range(timerange) == directory_list def testNoDateDirectory_new_format(): @@ -127,8 +127,8 @@ def test_directory_range_hours(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y%m%d_%H/%H%M.csv') - timerange = TimeRange('2009-12-31T23:40:00', '2010-01-01T01:15:00') - assert len(s.range(timerange)) == 3 # 3 directories (1 per hour) + timerange = TimeRange('2009-12-31T23:40:00', '2010-01-01T01:15:00') + assert len(s.range(timerange)) == 3 # 3 directories (1 per hour) def test_directory_range_hours_new_format(): @@ -140,10 +140,10 @@ def test_directory_range_single(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y%m%d/%H_%M.csv') - startdate = parse_time((2010, 10, 10, 5, 0)) - enddate = parse_time((2010, 10, 10, 7, 0)) - timerange = TimeRange(startdate, enddate) - assert len(s.range(timerange)) == 1 + startdate = parse_time((2010, 10, 10, 5, 0)) + enddate = parse_time((2010, 10, 10, 7, 0)) + timerange = TimeRange(startdate, enddate) + assert len(s.range(timerange)) == 1 def test_directory_range_single_new_format(): @@ -157,14 +157,14 @@ def test_directory_range_month(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y%m/%d/%j_%H.txt') - startdate = parse_time((2008, 2, 20, 10)) - enddate = parse_time((2008, 3, 2, 5)) - timerange = TimeRange(startdate, enddate) - assert len(s.range(timerange)) == 12 - startdate = parse_time((2009, 2, 20, 10)) - enddate = parse_time((2009, 3, 2, 5)) - timerange = TimeRange(startdate, enddate) - assert len(s.range(timerange)) == 11 + startdate = parse_time((2008, 2, 20, 10)) + enddate = parse_time((2008, 3, 2, 5)) + timerange = TimeRange(startdate, enddate) + assert len(s.range(timerange)) == 12 + startdate = parse_time((2009, 2, 20, 10)) + enddate = parse_time((2009, 3, 2, 5)) + timerange = TimeRange(startdate, enddate) + assert len(s.range(timerange)) == 11 def test_directory_range_month_new_format(): @@ -183,54 +183,58 @@ with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): # Standard pattern s = Scraper('data/%Y/%m/%d/fits/swap/swap_00174_fd_%Y%m%d_%H%M%S.fts.gz') - test_url = 'data/2014/05/14/fits/swap/swap_00174_fd_20140514_200135.fts.gz' - timeURL = parse_time((2014, 5, 14, 20, 1, 35)) - assert s._extract_date(test_url) == timeURL + test_url = 'data/2014/05/14/fits/swap/swap_00174_fd_20140514_200135.fts.gz' + timeURL = parse_time((2014, 5, 14, 20, 1, 35)) + assert s._extract_date(test_url) == timeURL + + with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): # Not-full repeated pattern s = Scraper('data/%Y/fits/swap/swap_00174_fd_%Y%m%d_%H%M%S.fts.gz') - test_url = 'data/2014/fits/swap/swap_00174_fd_20140514_200135.fts.gz' - timeURL = parse_time((2014, 5, 14, 20, 1, 35)) - assert s._extract_date(test_url) == timeURL + test_url = 'data/2014/fits/swap/swap_00174_fd_20140514_200135.fts.gz' + timeURL = parse_time((2014, 5, 14, 20, 1, 35)) + assert s._extract_date(test_url) == timeURL def test_extract_dates_not_separators(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('data/%Y/%m/swap%m%d_%H%M%S') - test_url = 'data/2014/05/swap0514_200135' - timeURL = parse_time((2014, 5, 14, 20, 1, 35)) - assert s._extract_date(test_url) == timeURL + test_url = 'data/2014/05/swap0514_200135' + timeURL = parse_time((2014, 5, 14, 20, 1, 35)) + assert s._extract_date(test_url) == timeURL def test_extract_dates_not_separators_and_similar(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('data/%Y/Jun%b%d_%H%M%S') - test_url = 'data/2014/JunJune14_200135' - timeURL = parse_time((2014, 6, 14, 20, 1, 35)) - assert s._extract_date(test_url) == timeURL - test_url = 'data/2014/JunMay14_200135' - timeURL = parse_time((2014, 5, 14, 20, 1, 35)) - assert s._extract_date(test_url) == timeURL + test_url = 'data/2014/JunJune14_200135' + timeURL = parse_time((2014, 6, 14, 20, 1, 35)) + assert s._extract_date(test_url) == timeURL + test_url = 'data/2014/JunMay14_200135' + timeURL = parse_time((2014, 5, 14, 20, 1, 35)) + assert s._extract_date(test_url) == timeURL + + with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): # and testing with the month afterwards s = Scraper('data/%Y/%dJun%b_%H%M%S') - test_url = 'data/2014/14JunJune_200135' - timeURL = parse_time((2014, 6, 14, 20, 1, 35)) - assert s._extract_date(test_url) == timeURL + test_url = 'data/2014/14JunJune_200135' + timeURL = parse_time((2014, 6, 14, 20, 1, 35)) + assert s._extract_date(test_url) == timeURL def test_url(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('fd_%Y%m%d_%H%M%S.fts') - assert s._url_follows_pattern('fd_20130410_231211.fts') - assert not s._url_follows_pattern('fd_20130410_231211.fts.gz') - assert not s._url_follows_pattern('fd_20130410_ar_231211.fts.gz') + assert s._url_follows_pattern('fd_20130410_231211.fts') + assert not s._url_follows_pattern('fd_20130410_231211.fts.gz') + assert not s._url_follows_pattern('fd_20130410_ar_231211.fts.gz') def test_url_pattern(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('fd_%Y%m%d_%H%M%S.fts') - assert s._url_follows_pattern('fd_20130410_231211.fts') - assert not s._url_follows_pattern('fd_20130410_231211.fts.gz') - assert not s._url_follows_pattern('fd_20130410_ar_231211.fts.gz') + assert s._url_follows_pattern('fd_20130410_231211.fts') + assert not s._url_follows_pattern('fd_20130410_231211.fts.gz') + assert not s._url_follows_pattern('fd_20130410_ar_231211.fts.gz') @pytest.mark.parametrize(('pattern', 'filename', 'metadict'), [ @@ -246,20 +250,20 @@ def test_url_pattern_milliseconds_generic(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('fd_%Y%m%d_%H%M%S_%e.fts') - assert s._url_follows_pattern('fd_20130410_231211_119.fts') - assert not s._url_follows_pattern('fd_20130410_231211.fts.gz') - assert not s._url_follows_pattern('fd_20130410_ar_231211.fts.gz') + assert s._url_follows_pattern('fd_20130410_231211_119.fts') + assert not s._url_follows_pattern('fd_20130410_231211.fts.gz') + assert not s._url_follows_pattern('fd_20130410_ar_231211.fts.gz') def test_url_pattern_milliseconds_zero_padded(): + now_mock = Mock(return_value=datetime.datetime(2019, 4, 19, 0, 0, 0, 4009)) with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): # Asserts solution to ticket #1954. # Milliseconds must be zero-padded in order to match URL lengths. - now_mock = Mock(return_value=datetime.datetime(2019, 4, 19, 0, 0, 0, 4009)) with patch('sunpy.net.scraper.datetime', now=now_mock): s = Scraper('fd_%Y%m%d_%H%M%S_%e.fts') - now_mock.assert_called_once() - assert s.now == 'fd_20190419_000000_004.fts' + now_mock.assert_called_once() + assert s.now == 'fd_20190419_000000_004.fts' def test_url_pattern_milliseconds_zero_padded_new_format(): @@ -276,12 +280,12 @@ with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('/'.join(['file:/', str(rootdir), 'EIT_header', 'efz%Y%m%d.%H%M%S_s.header'])) - startdate = parse_time((2004, 3, 1, 4, 0)) - enddate = parse_time((2004, 3, 1, 6, 30)) - assert len(s.filelist(TimeRange(startdate, enddate))) == 3 - startdate = parse_time((2010, 1, 10, 20, 30)) - enddate = parse_time((2010, 1, 20, 20, 30)) - assert len(s.filelist(TimeRange(startdate, enddate))) == 0 + startdate = parse_time((2004, 3, 1, 4, 0)) + enddate = parse_time((2004, 3, 1, 6, 30)) + assert len(s.filelist(TimeRange(startdate, enddate))) == 3 + startdate = parse_time((2010, 1, 10, 20, 30)) + enddate = parse_time((2010, 1, 20, 20, 30)) + assert len(s.filelist(TimeRange(startdate, enddate))) == 0 def test_files_range_same_directory_local_new_format(): @@ -296,18 +300,18 @@ @pytest.mark.remote_data def test_files_range_same_directory_remote(): + pattern = ('http://proba2.oma.be/{instrument}/data/bsd/%Y/%m/%d/' + '{instrument}_lv1_%Y%m%d_%H%M%S.fits') with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - pattern = ('http://proba2.oma.be/{instrument}/data/bsd/%Y/%m/%d/' - '{instrument}_lv1_%Y%m%d_%H%M%S.fits') s = Scraper(pattern, instrument='swap') - startdate = parse_time((2014, 5, 14, 0, 0)) - enddate = parse_time((2014, 5, 14, 0, 5)) - timerange = TimeRange(startdate, enddate) - assert len(s.filelist(timerange)) == 2 - startdate = parse_time((2014, 5, 14, 0, 6)) - enddate = parse_time((2014, 5, 14, 0, 7)) - timerange = TimeRange(startdate, enddate) - assert len(s.filelist(timerange)) == 0 + startdate = parse_time((2014, 5, 14, 0, 0)) + enddate = parse_time((2014, 5, 14, 0, 5)) + timerange = TimeRange(startdate, enddate) + assert len(s.filelist(timerange)) == 2 + startdate = parse_time((2014, 5, 14, 0, 6)) + enddate = parse_time((2014, 5, 14, 0, 7)) + timerange = TimeRange(startdate, enddate) + assert len(s.filelist(timerange)) == 0 @pytest.mark.remote_data @@ -327,17 +331,17 @@ @pytest.mark.remote_data def test_files_range_same_directory_months_remote(): + pattern = ('http://www.srl.caltech.edu/{spacecraft}/DATA/{instrument}/' + 'Ahead/1minute/AeH%y%b.1m') with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - pattern = ('http://www.srl.caltech.edu/{spacecraft}/DATA/{instrument}/' - 'Ahead/1minute/AeH%y%b.1m') s = Scraper(pattern, spacecraft='STEREO', instrument='HET') - startdate = parse_time((2007, 8, 1)) - enddate = parse_time((2007, 9, 10)) - timerange = TimeRange(startdate, enddate) - files = s.filelist(timerange) - assert files == ['http://www.srl.caltech.edu/STEREO/DATA/HET/Ahead/1minute/AeH07Aug.1m', - 'http://www.srl.caltech.edu/STEREO/DATA/HET/Ahead/1minute/AeH07Jul.1m', - 'http://www.srl.caltech.edu/STEREO/DATA/HET/Ahead/1minute/AeH07Sep.1m'] + startdate = parse_time((2007, 8, 1)) + enddate = parse_time((2007, 9, 10)) + timerange = TimeRange(startdate, enddate) + files = s.filelist(timerange) + assert files == ['http://www.srl.caltech.edu/STEREO/DATA/HET/Ahead/1minute/AeH07Aug.1m', + 'http://www.srl.caltech.edu/STEREO/DATA/HET/Ahead/1minute/AeH07Jul.1m', + 'http://www.srl.caltech.edu/STEREO/DATA/HET/Ahead/1minute/AeH07Sep.1m'] @pytest.mark.remote_data @@ -356,13 +360,13 @@ @pytest.mark.xfail @pytest.mark.remote_data def test_ftp(): + pattern = 'ftp://ftp.ngdc.noaa.gov/STP/swpc_products/daily_reports/solar_region_summaries/%Y/%m/%Y%m%dSRS.txt' with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - pattern = 'ftp://ftp.ngdc.noaa.gov/STP/swpc_products/daily_reports/solar_region_summaries/%Y/%m/%Y%m%dSRS.txt' s = Scraper(pattern) - timerange = TimeRange('2024/5/18', '2024/5/20') - urls = s.filelist(timerange) - assert urls[0] == ('ftp://ftp.ngdc.noaa.gov/STP/swpc_products/daily_reports/solar_region_summaries/2024/05/20240517SRS.txt') - assert len(urls) == 4 + timerange = TimeRange('2024/5/18', '2024/5/20') + urls = s.filelist(timerange) + assert urls[0] == ('ftp://ftp.ngdc.noaa.gov/STP/swpc_products/daily_reports/solar_region_summaries/2024/05/20240517SRS.txt') + assert len(urls) == 4 @pytest.mark.xfail @@ -388,27 +392,27 @@ @pytest.mark.remote_data def test_filelist_url_missing_directory(): + # Asserts solution to ticket #2684. + # Attempting to access data for the year 1960 results in a 404, so no files are returned. + pattern = 'http://lasp.colorado.edu/eve/data_access/evewebdataproducts/level2/%Y/%j/' with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - # Asserts solution to ticket #2684. - # Attempting to access data for the year 1960 results in a 404, so no files are returned. - pattern = 'http://lasp.colorado.edu/eve/data_access/evewebdataproducts/level2/%Y/%j/' s = Scraper(pattern) - timerange = TimeRange('1960/01/01 00:00:00', '1960/01/02 00:00:00') - assert len(s.filelist(timerange)) == 0 + timerange = TimeRange('1960/01/01 00:00:00', '1960/01/02 00:00:00') + assert len(s.filelist(timerange)) == 0 @pytest.mark.remote_data def test_filelist_relative_hrefs(): + # the url opened by the scraper from below pattern contains some links which don't have hrefs + pattern = 'http://www.bbso.njit.edu/pub/archive/%Y/%m/%d/bbso_halph_fr_%Y%m%d_%H%M%S.fts' with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - # the url opened by the scraper from below pattern contains some links which don't have hrefs - pattern = 'http://www.bbso.njit.edu/pub/archive/%Y/%m/%d/bbso_halph_fr_%Y%m%d_%H%M%S.fts' s = Scraper(pattern) - timerange = TimeRange('2016/5/18 15:28:00', '2016/5/18 16:30:00') - assert s.domain == 'http://www.bbso.njit.edu/' - # hrefs are relative to domain here, not to the directory they are present in - # this checks that `scraper.filelist` returns fileurls relative to the domain - fileurls = s.filelist(timerange) - assert fileurls[1] == s.domain + 'pub/archive/2016/05/18/bbso_halph_fr_20160518_160033.fts' + timerange = TimeRange('2016/5/18 15:28:00', '2016/5/18 16:30:00') + assert s.domain == 'http://www.bbso.njit.edu/' + # hrefs are relative to domain here, not to the directory they are present in + # this checks that `scraper.filelist` returns fileurls relative to the domain + fileurls = s.filelist(timerange) + assert fileurls[1] == s.domain + 'pub/archive/2016/05/18/bbso_halph_fr_20160518_160033.fts' @pytest.mark.remote_data @@ -431,33 +435,33 @@ def test_regex(pattern, check_file): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper(pattern, regex=True) - assert s._url_follows_pattern(check_file) + assert s._url_follows_pattern(check_file) @pytest.mark.remote_data def test_regex_data(): + prefix = r'https://gong2.nso.edu/oQR/zqs/' + pattern = prefix + r'%Y%m/mrzqs%y%m%d/mrzqs%y%m%dt%H%Mc(\d){4}_(\d){3}\.fits.gz' with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - prefix = r'https://gong2.nso.edu/oQR/zqs/' - pattern = prefix + r'%Y%m/mrzqs%y%m%d/mrzqs%y%m%dt%H%Mc(\d){4}_(\d){3}\.fits.gz' s = Scraper(pattern, regex=True) - timerange = TimeRange('2020-01-05', '2020-01-06T16:00:00') - assert s._url_follows_pattern(prefix + '202001/mrzqs200106/mrzqs200106t1514c2226_297.fits.gz') - assert len(s.filelist(timerange)) == 37 + timerange = TimeRange('2020-01-05', '2020-01-06T16:00:00') + assert s._url_follows_pattern(prefix + '202001/mrzqs200106/mrzqs200106t1514c2226_297.fits.gz') + assert len(s.filelist(timerange)) == 37 @pytest.mark.remote_data def test_extract_files_meta(): + prefix = r'https://gong2.nso.edu/oQR/zqs/' + baseurl1 = prefix + r'%Y%m/mrzqs%y%m%d/mrzqs%y%m%dt%H%Mc(\d){4}_(\d){3}\.fits.gz' + extractpattern1 = ('{}/zqs/{year:4d}{month:2d}/mrzqs{:4d}{day:2d}/mrzqs{:6d}t' + '{hour:2d}{minute:2d}c{CAR_ROT:4d}_{:3d}.fits.gz') with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - prefix = r'https://gong2.nso.edu/oQR/zqs/' - baseurl1 = prefix + r'%Y%m/mrzqs%y%m%d/mrzqs%y%m%dt%H%Mc(\d){4}_(\d){3}\.fits.gz' - extractpattern1 = ('{}/zqs/{year:4d}{month:2d}/mrzqs{:4d}{day:2d}/mrzqs{:6d}t' - '{hour:2d}{minute:2d}c{CAR_ROT:4d}_{:3d}.fits.gz') s1 = Scraper(baseurl1, regex=True) - timerange1 = TimeRange('2020-01-05', '2020-01-05T16:00:00') - metalist1 = s1._extract_files_meta(timerange1, extractpattern1) - urls = s1.filelist(timerange1) - assert metalist1[3]['CAR_ROT'] == 2226 - assert metalist1[-1]['url'] == urls[-1] + timerange1 = TimeRange('2020-01-05', '2020-01-05T16:00:00') + metalist1 = s1._extract_files_meta(timerange1, extractpattern1) + urls = s1.filelist(timerange1) + assert metalist1[3]['CAR_ROT'] == 2226 + assert metalist1[-1]['url'] == urls[-1] @pytest.mark.remote_data @@ -484,10 +488,10 @@ def test_no_directory(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('files/%Y%m%d_%H%M.dat') - startdate = parse_time((2010, 1, 10, 20, 30)) - enddate = parse_time((2010, 1, 20, 20, 30)) - timerange = TimeRange(startdate, enddate) - assert len(s.range(timerange)) == 1 + startdate = parse_time((2010, 1, 10, 20, 30)) + enddate = parse_time((2010, 1, 20, 20, 30)) + timerange = TimeRange(startdate, enddate) + assert len(s.range(timerange)) == 1 def test_no_directory_new_format(): s = Scraper(format='files/{{year:4d}}{{month:2d}}{{day:2d}}_{{hour:2d}}{{month:2d}}.dat') @@ -519,15 +523,15 @@ @pytest.mark.remote_data def test_yearly_overlap(): + # Check that a time range that falls within the interval that a file represents + # returns a single result. + pattern = "https://www.ngdc.noaa.gov/stp/space-weather/solar-data/solar-features/solar-flares/x-rays/goes/xrs/goes-xrs-report_%Y.txt" with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): - # Check that a time range that falls within the interval that a file represents - # returns a single result. - pattern = "https://www.ngdc.noaa.gov/stp/space-weather/solar-data/solar-features/solar-flares/x-rays/goes/xrs/goes-xrs-report_%Y.txt" scraper = Scraper(pattern) - # Should return a single file for 2013 - trange = TimeRange("2013-01-02", "2013-01-03") - assert len(scraper.filelist(trange)) == 1 + # Should return a single file for 2013 + trange = TimeRange("2013-01-02", "2013-01-03") + assert len(scraper.filelist(trange)) == 1 @pytest.mark.remote_data def test_yearly_overlap_new_format(): @@ -592,26 +596,26 @@ def test_check_timerange(): with pytest.warns(SunpyDeprecationWarning, match="pattern has been replaced with the format keyword"): s = Scraper('%Y.fits') - # Valid time range for 2014.fits is the whole of 2014 - # Test different cases to make sure check_timerange is working as expected + # Valid time range for 2014.fits is the whole of 2014 + # Test different cases to make sure check_timerange is working as expected - # Interval exactly on lower boundary - assert s._check_timerange('2014.fits', TimeRange("2013-06-01", "2014-01-01")) - # Overlaps lower boundary - assert s._check_timerange('2014.fits', TimeRange("2013-06-01", "2014-01-02")) - # Overlaps upper and lower boundary - assert s._check_timerange('2014.fits', TimeRange("2013-06-01", "2015-01-02")) - # Entirely within both boundaries - assert s._check_timerange('2014.fits', TimeRange("2014-06-01", "2014-07-02")) - # Overlaps upper boundary - assert s._check_timerange('2014.fits', TimeRange("2014-06-01", "2015-01-02")) - # Interval exactly on upper boundary - assert s._check_timerange('2014.fits', TimeRange("2015-01-01", "2015-01-02")) - - # Interval below both boundaries - assert not s._check_timerange('2014.fits', TimeRange("2002-01-01", "2013-01-02")) - # Interval above both boundaries - assert not s._check_timerange('2014.fits', TimeRange("2022-01-01", "2025-01-02")) + # Interval exactly on lower boundary + assert s._check_timerange('2014.fits', TimeRange("2013-06-01", "2014-01-01")) + # Overlaps lower boundary + assert s._check_timerange('2014.fits', TimeRange("2013-06-01", "2014-01-02")) + # Overlaps upper and lower boundary + assert s._check_timerange('2014.fits', TimeRange("2013-06-01", "2015-01-02")) + # Entirely within both boundaries + assert s._check_timerange('2014.fits', TimeRange("2014-06-01", "2014-07-02")) + # Overlaps upper boundary + assert s._check_timerange('2014.fits', TimeRange("2014-06-01", "2015-01-02")) + # Interval exactly on upper boundary + assert s._check_timerange('2014.fits', TimeRange("2015-01-01", "2015-01-02")) + + # Interval below both boundaries + assert not s._check_timerange('2014.fits', TimeRange("2002-01-01", "2013-01-02")) + # Interval above both boundaries + assert not s._check_timerange('2014.fits', TimeRange("2022-01-01", "2025-01-02")) def test_check_timerange_new_pattern(): s = Scraper(format='{{year:4d}}.fits') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/physics/tests/test_differential_rotation.py new/sunpy-7.0.1/sunpy/physics/tests/test_differential_rotation.py --- old/sunpy-7.0.0/sunpy/physics/tests/test_differential_rotation.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/physics/tests/test_differential_rotation.py 2025-07-31 16:11:53.000000000 +0200 @@ -345,9 +345,12 @@ with pytest.warns(RuntimeWarning, match='All-NaN axis encountered'): assert _get_extreme_position(coords, 'Tx', operator=np.nanmin) == -1 + with pytest.warns(RuntimeWarning, match='All-NaN axis encountered'): assert _get_extreme_position(coords, 'Ty', operator=np.nanmin) == -2 + with pytest.warns(RuntimeWarning, match='All-NaN axis encountered'): assert _get_extreme_position(coords, 'Tx', operator=np.nanmax) == 1 + with pytest.warns(RuntimeWarning, match='All-NaN axis encountered'): assert _get_extreme_position(coords, 'Ty', operator=np.nanmax) == 2 with pytest.raises(ValueError, match="The \"axis\" argument must be either \"Tx\" or \"Ty\""): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json new/sunpy-7.0.1/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json --- old/sunpy-7.0.0/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json 2025-07-31 16:11:53.000000000 +0200 @@ -54,12 +54,12 @@ "sunpy.map.tests.test_plotting.test_draw_limb_different_observer": "bba8a5d3d8e5e3e3a7d36060d9730e4545c5250367902cf97048c33049f330f3", "sunpy.map.tests.test_plotting.test_draw_limb_heliographic_stonyhurst": "fcbf6be2894be891f458d2bc305702564cb451052bc727b0175e7b6ad527801c", "sunpy.map.tests.test_plotting.test_map_draw_extent": "c51113113a46c838b07b049a3f17940184b99b0a56200fbd63e6cf2bf1e3c86c", - "sunpy.map.tests.test_plotting.test_plot_autoalign[True]": "800c1148cc30e8ddc540aa555a488f9ed8ba654cd3e055bdd4a08238c2542c9e", + "sunpy.map.tests.test_plotting.test_plot_autoalign[True]": "c86e4af4907f01f104f38bb56746744ee99a95fe85dfd843d7baa56723265f3e", "sunpy.map.tests.test_plotting.test_plot_autoalign[mesh]": "197f06eced7fe4bf8496cf5f3ba4e4cc379ac108be0f212d605007c1e3db062c", - "sunpy.map.tests.test_plotting.test_plot_autoalign[image]": "800c1148cc30e8ddc540aa555a488f9ed8ba654cd3e055bdd4a08238c2542c9e", + "sunpy.map.tests.test_plotting.test_plot_autoalign[image]": "c86e4af4907f01f104f38bb56746744ee99a95fe85dfd843d7baa56723265f3e", "sunpy.map.tests.test_plotting.test_plot_autoalign_datalim": "a5af424f5d70394d4e39a7066793463bec0adae17c9ab46a451640957822bc8d", - "sunpy.map.tests.test_plotting.test_plot_autoalign_pixel_alignment": "88d04e77285edf867f2cc3a77a19adbf75a61df68a34bb57359397b5dab0f4d1", - "sunpy.map.tests.test_plotting.test_plot_autoalign_image_incomplete": "325ee598aa6f2cd4ab8e036d4266f408ea9b5988e1823c3ff22e0eab8584ce5c", + "sunpy.map.tests.test_plotting.test_plot_autoalign_pixel_alignment": "411b3ea8353e00f644225c866c55fc6532c855ea8e19dd8355965fdbbef2de20", + "sunpy.map.tests.test_plotting.test_plot_autoalign_image_incomplete": "fc2520d17414d0f742e7c62ed0a189f311f5f468329b20dbf714e71b0a5fa7fa", "sunpy.map.tests.test_reproject_to.test_reproject_to_hgs": "90452e9e8f248f8798a794ba70e157bf0eb87a083bda55a8ffc846c2024b1363", "sunpy.map.tests.test_reproject_to.test_reproject_to_hpc_interpolation": "adca79ed26b0139f29c5c0bec63d1a818fe8ba0cd718923d952210e6e8ca801c", "sunpy.map.tests.test_reproject_to.test_reproject_to_hpc_exact": "a772b4ba8deeb611c683d86911bf0b5acb90a3b307b1064c1d04511050810eed", @@ -90,4 +90,4 @@ "sunpy.visualization.tests.test_drawing.test_draw_extent": "d9e5182e20feff6257c3209c2202727a8e628842ca3c0c80fd1cafbc64376388", "sunpy.visualization.tests.test_drawing.test_draw_extent_3d": "1d329ecd0e31f4d4ecfb3b75e255c117dc631bd39be9a8a862fb7974004ff07c", "sunpy.visualization.tests.test_visualization.test_show_hpr_impact_angle": "43f6c3fea4238064ce503d699cba6457b3eb07ee58ea2a52f43556f42ee7efc8" -} +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/time/tests/test_time.py new/sunpy-7.0.1/sunpy/time/tests/test_time.py --- old/sunpy-7.0.0/sunpy/time/tests/test_time.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/time/tests/test_time.py 2025-07-31 16:11:53.000000000 +0200 @@ -206,6 +206,7 @@ ('2007-05-04T21:08:12.999999', '2007:124:21:08:12.999999'), ('2007-05-04T21:08:12', '2007-05-04T21:08:12'), ('2007-05-04T21:08:12', '2007/05/04T21:08:12'), + ('2007-05-04T21:08:12', '2007-05-04T21:08:12Z'), ('2007-05-04T21:08:12', '20070504T210812'), ('2007-05-04T21:08:12', '2007/05/04 21:08:12'), ('2007-05-04T21:08:12', '2007-05-04 21:08:12'), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/time/time.py new/sunpy-7.0.1/sunpy/time/time.py --- old/sunpy-7.0.0/sunpy/time/time.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/time/time.py 2025-07-31 16:11:53.000000000 +0200 @@ -47,6 +47,7 @@ "%Y-%m-%dT%H:%M:%S.%fZ", # Example 2007-05-04T21:08:12.999Z "%Y-%m-%dT%H:%M:%S", # Example 2007-05-04T21:08:12 "%Y/%m/%dT%H:%M:%S", # Example 2007/05/04T21:08:12 + "%Y-%m-%dT%H:%M:%SZ", # Example 2007-05-04T21:08:12Z "%Y%m%dT%H%M%S.%f", # Example 20070504T210812.999999 "%Y%m%dT%H%M", # Example 20070504T2108 , Should precede "%Y%m%dT%H%M%S". "%Y%m%dT%H%M%S", # Example 20070504T210812 @@ -252,6 +253,8 @@ except ValueError: pass + log.debug("No matching sunpy format found for %s, so falling back to astropy formats", first_item) + # If the string format does not match one of ours, we need to protect against a bad interaction # between astropy's C fast parser and numpy>=2.3 # https://github.com/astropy/astropy/issues/18254 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/timeseries/tests/test_timeseriesbase.py new/sunpy-7.0.1/sunpy/timeseries/tests/test_timeseriesbase.py --- old/sunpy-7.0.0/sunpy/timeseries/tests/test_timeseriesbase.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/timeseries/tests/test_timeseriesbase.py 2025-07-31 16:11:53.000000000 +0200 @@ -553,7 +553,7 @@ data = np.array([times, intensity]).T with pytest.warns(SunpyUserWarning, match='Unknown units'): ts = sunpy.timeseries.TimeSeries(data, {}) - assert isinstance(ts, sunpy.timeseries.GenericTimeSeries) + assert isinstance(ts, sunpy.timeseries.GenericTimeSeries) # TODO: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/timeseries/tests/test_timeseriesmetadata.py new/sunpy-7.0.1/sunpy/timeseries/tests/test_timeseriesmetadata.py --- old/sunpy-7.0.0/sunpy/timeseries/tests/test_timeseriesmetadata.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/timeseries/tests/test_timeseriesmetadata.py 2025-07-31 16:11:53.000000000 +0200 @@ -367,7 +367,7 @@ assert updated_dict_md == updated_ordereddict_md == updated_metadict_md -def test_update_overwrite(complex_append_md, overwrite=True): +def test_update_overwrite(complex_append_md): updated_not_overwritten_md = copy.deepcopy(complex_append_md) updated_not_overwritten_md.update({'all_same': 'updated'}) updated_overwritten_md = copy.deepcopy(complex_append_md) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/util/sysinfo.py new/sunpy-7.0.1/sunpy/util/sysinfo.py --- old/sunpy-7.0.0/sunpy/util/sysinfo.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/util/sysinfo.py 2025-07-31 16:11:53.000000000 +0200 @@ -1,6 +1,7 @@ """ This module provides functions to retrieve system information. """ +import sys import platform from collections import defaultdict from importlib.metadata import PackageNotFoundError, version, requires, distribution @@ -14,7 +15,19 @@ __all__ = ['system_info', 'find_dependencies', 'missing_dependencies_by_extra'] -def get_requirements(package): +def _trusted_version(package_name): + """ + If the package has already been imported, trust its __version__ attribute + over the version retrievable by importlib. + """ + if package_name in sys.modules: + package = sys.modules[package_name] + if hasattr(package, "__version__"): + return package.__version__ + return version(package_name) + + +def get_requirements(package, *, expand_groups=False): """ This wraps `importlib.metadata.requires` to not be garbage. @@ -23,6 +36,9 @@ package : str Package you want requirements for. + expand_groups : bool + If `True`, expand any requirement that is a group of the specified package. + Returns ------- `dict` @@ -43,9 +59,34 @@ if package_name in requires_dict[group]: continue requires_dict[group][package_name] = req + + if expand_groups: + # First expand each self-referential package requirement into the individual groups + for group, group_reqs in requires_dict.items(): + if package in group_reqs and (extras := group_reqs[package].extras): + requires_dict[group].update(dict.fromkeys(extras)) + del group_reqs[package] + + # Resolve each group, recursing as necessary + for group in requires_dict: + _resolve_group(group, requires_dict) + return requires_dict +def _resolve_group(group, requires_dict): + """ + Return a fully resolved list of requirements for a group. + If the group requires another group, recurse to fully resolve that group first. + The dictionary is permanently updated with the fully resolved requirements. + """ + for req_name, req in requires_dict[group].copy().items(): + if req is None: # req==None means that req_name is an unresolved group + requires_dict[group].update(_resolve_group(req_name, requires_dict)) + del requires_dict[group][req_name] + return requires_dict[group] + + def resolve_requirement_versions(package_versions): """ Resolves a list of requirements for the same package. @@ -82,7 +123,7 @@ which should be installed to match the requirements and return any which are missing. """ - requirements = get_requirements(package) + requirements = get_requirements(package, expand_groups=True) installed_requirements = {} missing_requirements = defaultdict(list) extras = extras or ["required"] @@ -91,7 +132,7 @@ continue for package, package_details in requirements[group].items(): try: - package_version = version(package) + package_version = _trusted_version(package) installed_requirements[package] = package_version except PackageNotFoundError: missing_requirements[package].append(package_details) @@ -125,7 +166,7 @@ dependencies associated with no extras. """ exclude_extras = exclude_extras or [] - requirements = get_requirements(package) + requirements = get_requirements(package) # groups do not need to be expanded here missing_dependencies = {} for group in requirements.keys(): if group in exclude_extras: @@ -149,7 +190,7 @@ """ Prints ones' system info in an "attractive" fashion. """ - requirements = get_requirements("sunpy") + requirements = get_requirements("sunpy", expand_groups=True) groups = get_keys_list(requirements) extra_groups = get_extra_groups(groups, ['all', 'dev']) base_reqs = get_keys_list(requirements['required']) @@ -158,7 +199,7 @@ extra_prop = {"System": platform.system(), "Arch": f"{platform.architecture()[0]}, ({platform.processor()})", "Python": platform.python_version(), - "sunpy": version("sunpy")} + "sunpy": _trusted_version("sunpy")} sys_prop = {**installed_packages, **missing_packages, **extra_prop} print("==============================") print("sunpy Installation Information") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/util/tests/test_decorators.py new/sunpy-7.0.1/sunpy/util/tests/test_decorators.py --- old/sunpy-7.0.0/sunpy/util/tests/test_decorators.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/util/tests/test_decorators.py 2025-07-31 16:11:53.000000000 +0200 @@ -21,7 +21,7 @@ @deprecated(since, message=message) def foo(): pass - with pytest.warns(warning, match=warning_message): + with pytest.warns(warning, match=warning_message): # NOQA: PT031 warnings.simplefilter('always') foo() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/util/tests/test_logger.py new/sunpy-7.0.1/sunpy/util/tests/test_logger.py --- old/sunpy-7.0.0/sunpy/util/tests/test_logger.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/util/tests/test_logger.py 2025-07-31 16:11:53.000000000 +0200 @@ -100,7 +100,7 @@ assert len(warn_list) == 1 # With warnings logging, making sure that Astropy warnings are not intercepted - with pytest.warns(AstropyUserWarning, match="This warning should not be captured") as warn_list: + with pytest.warns(AstropyUserWarning, match="This warning should not be captured") as warn_list: # NOQA: PT031 log.enable_warnings_logging() with log.log_to_list() as log_list: warnings.warn("This warning should be captured", SunpyUserWarning) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy/util/tests/test_sysinfo.py new/sunpy-7.0.1/sunpy/util/tests/test_sysinfo.py --- old/sunpy-7.0.0/sunpy/util/tests/test_sysinfo.py 2025-06-18 17:14:41.000000000 +0200 +++ new/sunpy-7.0.1/sunpy/util/tests/test_sysinfo.py 2025-07-31 16:11:53.000000000 +0200 @@ -59,6 +59,9 @@ for package in EXTRA_DEPS: assert package in installed + # There should not be any self-referential dependency on sunpy + assert "sunpy" not in installed + def test_missing_dependencies_by_extra(): missing = missing_dependencies_by_extra() @@ -98,4 +101,10 @@ def test_system_info(capsys): system_info() captured = capsys.readouterr() - assert "\nsunpy Installation Information\n" in captured.out + lines = captured.out.splitlines() + assert "sunpy Installation Information" in lines + + # sunpy should not be listed as an optional dependency + index = lines.index("Optional Dependencies") + for line in lines[index + 2:]: + assert not line.startswith("sunpy:") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sunpy-7.0.0/sunpy.egg-info/PKG-INFO new/sunpy-7.0.1/sunpy.egg-info/PKG-INFO --- old/sunpy-7.0.0/sunpy.egg-info/PKG-INFO 2025-06-18 17:14:50.000000000 +0200 +++ new/sunpy-7.0.1/sunpy.egg-info/PKG-INFO 2025-07-31 16:12:05.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: sunpy -Version: 7.0.0 +Version: 7.0.1 Summary: SunPy core package: Python for Solar Physics Author-email: The SunPy Community <su...@googlegroups.com> License: Copyright (c) 2013-2025 The SunPy developers