From 33aa541d06e6ddef66acaf1dae8b74cb89889c46 Mon Sep 17 00:00:00 2001 From: Nabil Freij Date: Mon, 16 Dec 2024 15:21:36 -0800 Subject: [PATCH] moose --- .isort.cfg | 4 +- .pre-commit-config.yaml | 10 +-- .ruff.toml | 81 ----------------------- aiapy/__init__.py | 10 +++ aiapy/_dev/__init__.py | 5 +- aiapy/_dev/scm_version.py | 3 +- aiapy/calibrate/meta.py | 7 +- aiapy/calibrate/prep.py | 4 +- aiapy/calibrate/spikes.py | 10 ++- aiapy/calibrate/tests/test_meta.py | 18 ++--- aiapy/calibrate/tests/test_prep.py | 33 +++++---- aiapy/calibrate/tests/test_spikes.py | 18 ++--- aiapy/calibrate/tests/test_uncertainty.py | 6 +- aiapy/calibrate/tests/test_util.py | 20 +++--- aiapy/calibrate/transform.py | 2 +- aiapy/calibrate/util.py | 2 +- aiapy/conftest.py | 2 +- aiapy/data/__init__.py | 3 + aiapy/data/_manager.py | 4 +- aiapy/data/_sample.py | 2 +- aiapy/data/sample.py | 6 +- aiapy/psf/__init__.py | 5 ++ aiapy/psf/deconvolve.py | 4 +- aiapy/psf/tests/test_deconvolve.py | 10 +-- aiapy/psf/tests/test_psf.py | 4 +- aiapy/response/channel.py | 2 +- aiapy/response/tests/test_channel.py | 32 ++++----- aiapy/tests/test_idl.py | 4 +- aiapy/tests/test_init.py | 2 +- aiapy/util/decorators.py | 5 ++ aiapy/util/exceptions.py | 8 +-- aiapy/util/tests/test_util.py | 4 +- aiapy/util/util.py | 2 +- aiapy/version.py | 7 +- docs/conf.py | 4 +- docs/reference/util.rst | 2 +- ruff.toml | 10 ++- 37 files changed, 156 insertions(+), 199 deletions(-) delete mode 100644 .ruff.toml diff --git a/.isort.cfg b/.isort.cfg index 3db3d1d..dfec217 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -6,11 +6,11 @@ skip = default_section = THIRDPARTY include_trailing_comma = true known_astropy = astropy, asdf -known_sunpy = sunpy +known_sunpy = sunpy, drms known_first_party = aiapy length_sort = false length_sort_sections = stdlib -line_length = 110 +line_length = 120 multi_line_output = 3 no_lines_before = LOCALFOLDER sections = STDLIB, THIRDPARTY, ASTROPY, SUNPY, FIRSTPARTY, LOCALFOLDER diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index accc974..6004960 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,10 @@ +exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*|extern.*|aiapy/extern|^CITATION.rst)$" repos: + - repo: https://github.com/PyCQA/docformatter + rev: master + hooks: + - id: docformatter + args: ["--in-place", "--pre-summary-newline", "--make-summary-multi"] # This should be before any formatting hooks like isort - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.8.2" @@ -10,22 +16,18 @@ repos: rev: 5.13.2 hooks: - id: isort - exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*|extern.*|aiapy/extern)$" - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-ast - id: check-case-conflict - id: trailing-whitespace - exclude: ".*(.fits|.fts|.fit|.header|.txt)$" - id: check-yaml - id: debug-statements - id: check-added-large-files args: ["--enforce-all", "--maxkb=1054"] - id: end-of-file-fixer - exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*|.json)$|^CITATION.rst$" - id: mixed-line-ending - exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*)$" - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: diff --git a/.ruff.toml b/.ruff.toml deleted file mode 100644 index c96c865..0000000 --- a/.ruff.toml +++ /dev/null @@ -1,81 +0,0 @@ -target-version = "py310" -line-length = 120 -exclude = [ - ".git,", - "__pycache__", - "build", - "aiapy/version.py", -] - -[lint] -select = [ - "E", - "F", - "W", - "UP", - "PT", - "BLE", - "A", - "C4", - "INP", - "PIE", - "T20", - "RET", - "TID", - "PTH", - "PD", - "PLC", - "PLE", - "FLY", - "NPY", - "PERF", - "RUF", -] -extend-ignore = [ - # pycodestyle (E, W) - "E501", # ignore line length will use a formatter instead - # pyupgrade (UP) - "UP038", # Use | in isinstance - not compatible with models and is slower - # pytest (PT) - "PT001", # Always use pytest.fixture() - "PT023", # Always use () on pytest decorators - # flake8-pie (PIE) - "PIE808", # Disallow passing 0 as the first argument to range - # flake8-use-pathlib (PTH) - "PTH123", # open() should be replaced by Path.open() - # Ruff (RUF) - "RUF003", # Ignore ambiguous quote marks, doesn't allow ' in comments - "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` - "RUF013", # PEP 484 prohibits implicit `Optional` - "RUF015", # Prefer `next(iter(...))` over single element slice -] - -[lint.per-file-ignores] -"setup.py" = [ - "INP001", # File is part of an implicit namespace package. -] -"conftest.py" = [ - "INP001", # File is part of an implicit namespace package. -] -"docs/conf.py" = [ - "E402" # Module imports not at top of file -] -"docs/*.py" = [ - "INP001", # File is part of an implicit namespace package. -] -"examples/**.py" = [ - "T201", # allow use of print in examples - "INP001", # File is part of an implicit namespace package. -] -"__init__.py" = [ - "E402", # Module level import not at top of cell - "F401", # Unused import - "F403", # from {name} import * used; unable to detect undefined names - "F405", # {name} may be undefined, or defined from star imports -] -"test_*.py" = [ - "E402", # Module level import not at top of cell -] - -[lint.pydocstyle] -convention = "numpy" diff --git a/aiapy/__init__.py b/aiapy/__init__.py index 6b123fd..092bbcc 100644 --- a/aiapy/__init__.py +++ b/aiapy/__init__.py @@ -1,3 +1,13 @@ +""" +``aiapy`` +========= + +Python library for AIA data analysis. + +* Homepage: https://aia.lmsal.com +* Documentation: https://aiapy.readthedocs.io/en/stable/ +""" + from itertools import compress from pathlib import Path diff --git a/aiapy/_dev/__init__.py b/aiapy/_dev/__init__.py index e38b3a8..52363c1 100644 --- a/aiapy/_dev/__init__.py +++ b/aiapy/_dev/__init__.py @@ -1,6 +1,7 @@ """ -This package contains utilities that are only used when developing in a -copy of the source repository. +This package contains utilities that are only used when developing in a copy of +the source repository. + These files are not installed, and should not be assumed to exist at runtime. """ diff --git a/aiapy/_dev/scm_version.py b/aiapy/_dev/scm_version.py index d0c4c1f..60c487c 100644 --- a/aiapy/_dev/scm_version.py +++ b/aiapy/_dev/scm_version.py @@ -9,4 +9,5 @@ except ImportError: raise except Exception as e: - raise ValueError("setuptools_scm can not determine version.") from e + msg = "setuptools_scm can not determine version." + raise ValueError(msg) from e diff --git a/aiapy/calibrate/meta.py b/aiapy/calibrate/meta.py index 6e9563e..328e61b 100644 --- a/aiapy/calibrate/meta.py +++ b/aiapy/calibrate/meta.py @@ -13,7 +13,7 @@ from sunpy.map import contains_full_disk from aiapy.calibrate.util import get_pointing_table -from aiapy.util.exceptions import AiapyUserWarning +from aiapy.util.exceptions import AIApyUserWarning __all__ = ["fix_observer_location", "update_pointing"] @@ -127,7 +127,8 @@ def update_pointing(smap, *, pointing_table=None): # NOTE: In sunpy >=6.0, the reference_date property was introduced which, for # AIA maps, will always be pulled from "T_OBS" t_obs_in_interval = np.logical_and( - smap.reference_date >= pointing_table["T_START"], smap.reference_date < pointing_table["T_STOP"] + smap.reference_date >= pointing_table["T_START"], + smap.reference_date < pointing_table["T_STOP"], ) if not t_obs_in_interval.any(): msg = ( @@ -170,7 +171,7 @@ def update_pointing(smap, *, pointing_table=None): # these cases, we just want to skip updating the pointing information. warnings.warn( f"Missing value in pointing table for {key}. This key will not be updated.", - AiapyUserWarning, + AIApyUserWarning, stacklevel=3, ) else: diff --git a/aiapy/calibrate/prep.py b/aiapy/calibrate/prep.py index cdd3665..483361f 100644 --- a/aiapy/calibrate/prep.py +++ b/aiapy/calibrate/prep.py @@ -14,7 +14,7 @@ from aiapy.calibrate.transform import _rotation_function_names from aiapy.calibrate.util import _select_epoch_from_correction_table, get_correction_table -from aiapy.util import AiapyUserWarning +from aiapy.util import AIApyUserWarning from aiapy.util.decorators import validate_channel __all__ = ["correct_degradation", "degradation", "register"] @@ -80,7 +80,7 @@ def register(smap, *, missing=None, order=3, method="scipy"): if smap.processing_level is None or smap.processing_level > 1: warnings.warn( "Image registration should only be applied to level 1 data", - AiapyUserWarning, + AIApyUserWarning, stacklevel=3, ) # Target scale is 0.6 arcsec/pixel, but this needs to be adjusted if the diff --git a/aiapy/calibrate/spikes.py b/aiapy/calibrate/spikes.py index bb619b6..83299d2 100644 --- a/aiapy/calibrate/spikes.py +++ b/aiapy/calibrate/spikes.py @@ -1,17 +1,21 @@ +""" +Functions for re-inserting "spikes" into level 1 AIA images. +""" + import copy import warnings -import drms import numpy as np import astropy.units as u from astropy.io import fits from astropy.wcs.utils import pixel_to_pixel +import drms from sunpy.map.mapbase import PixelPair from sunpy.map.sources.sdo import AIAMap -from aiapy.util import AiapyUserWarning +from aiapy.util import AIApyUserWarning __all__ = ["fetch_spikes", "respike"] @@ -85,7 +89,7 @@ def respike(smap, *, spikes=None): "in any way from the level 1 image, the spike data will likely be " "reinserted in the incorrect pixel positions." ), - AiapyUserWarning, + AIApyUserWarning, stacklevel=3, ) # FIXME: Should raise an exception? Or just return with a warning? diff --git a/aiapy/calibrate/tests/test_meta.py b/aiapy/calibrate/tests/test_meta.py index 8b32e4e..0f0a8f7 100644 --- a/aiapy/calibrate/tests/test_meta.py +++ b/aiapy/calibrate/tests/test_meta.py @@ -8,10 +8,10 @@ from aiapy.calibrate import fix_observer_location, update_pointing from aiapy.calibrate.util import get_pointing_table -from aiapy.util.exceptions import AiapyUserWarning +from aiapy.util.exceptions import AIApyUserWarning -def test_fix_observer_location(aia_171_map): +def test_fix_observer_location(aia_171_map) -> None: smap_fixed = fix_observer_location(aia_171_map) # NOTE: AIAMap already fixes the .observer_coordinate property with HAE assert smap_fixed.meta["hgln_obs"] == smap_fixed.observer_coordinate.lon.value @@ -47,7 +47,7 @@ def mock_pointing_table(): @pytest.mark.remote_data -def test_fix_pointing(aia_171_map, pointing_table): +def test_fix_pointing(aia_171_map, pointing_table) -> None: keys = ["CRPIX1", "CRPIX2", "CDELT1", "CDELT2", "CROTA2"] # Remove keys to at least test that they get set for k in keys: @@ -78,7 +78,7 @@ def test_fix_pointing(aia_171_map, pointing_table): (1.001, 1), ], ) -def test_update_pointing_accuracy(aia_171_map, pointing_table, t_delt_factor, expected_entry): +def test_update_pointing_accuracy(aia_171_map, pointing_table, t_delt_factor, expected_entry) -> None: t_start = pointing_table[0]["T_START"] t_delt = pointing_table[0]["T_STOP"] - t_start # This is nearly always 3 hours aia_171_map.meta["T_OBS"] = (t_start + t_delt * t_delt_factor).isot @@ -88,7 +88,7 @@ def test_update_pointing_accuracy(aia_171_map, pointing_table, t_delt_factor, ex @pytest.mark.remote_data -def test_update_pointing_submap_raises_exception(aia_171_map, pointing_table): +def test_update_pointing_submap_raises_exception(aia_171_map, pointing_table) -> None: m = aia_171_map.submap( SkyCoord(0, 0, unit="arcsec", frame=aia_171_map.coordinate_frame), top_right=aia_171_map.top_right_coord, @@ -98,14 +98,14 @@ def test_update_pointing_submap_raises_exception(aia_171_map, pointing_table): @pytest.mark.remote_data -def test_update_pointing_resampled_raises_exception(aia_171_map, pointing_table): +def test_update_pointing_resampled_raises_exception(aia_171_map, pointing_table) -> None: m = aia_171_map.resample((512, 512) * u.pixel) with pytest.raises(ValueError, match="Input must be at the full resolution"): update_pointing(m, pointing_table=pointing_table) @pytest.mark.remote_data -def test_update_pointing_no_entry_raises_exception(aia_171_map, pointing_table): +def test_update_pointing_no_entry_raises_exception(aia_171_map, pointing_table) -> None: # This tests that an exception is thrown when entry corresponding to # T_START <= T_OBS < T_END cannot be found in the pointing table. # We explicitly set the T_OBS key @@ -114,11 +114,11 @@ def test_update_pointing_no_entry_raises_exception(aia_171_map, pointing_table): update_pointing(aia_171_map, pointing_table=pointing_table) -def test_fix_pointing_missing_value(aia_171_map, mock_pointing_table): +def test_fix_pointing_missing_value(aia_171_map, mock_pointing_table) -> None: # Adjust map to a date we know has missing pointing information aia_171_map.meta["date-obs"] = "2010-09-30T06:51:48.344" aia_171_map.meta["t_obs"] = aia_171_map.meta["date-obs"] - with pytest.warns(AiapyUserWarning, match="Missing value in pointing table"): + with pytest.warns(AIApyUserWarning, match="Missing value in pointing table"): aia_171_map_updated = update_pointing(aia_171_map, pointing_table=mock_pointing_table) assert aia_171_map.meta["crpix1"] == aia_171_map_updated.meta["crpix1"] assert aia_171_map.meta["crpix2"] == aia_171_map_updated.meta["crpix2"] diff --git a/aiapy/calibrate/tests/test_prep.py b/aiapy/calibrate/tests/test_prep.py index a5f3c7d..04f5d6c 100644 --- a/aiapy/calibrate/tests/test_prep.py +++ b/aiapy/calibrate/tests/test_prep.py @@ -1,5 +1,4 @@ import copy -import tempfile import numpy as np import pytest @@ -14,7 +13,7 @@ from aiapy.calibrate import correct_degradation, degradation, register from aiapy.calibrate.util import get_correction_table from aiapy.tests.data import get_test_filepath -from aiapy.util import AiapyUserWarning +from aiapy.util import AIApyUserWarning @pytest.fixture @@ -27,7 +26,7 @@ def non_sdo_map(): return Map(sunpy.data.test.get_test_filepath("hsi_image_20101016_191218.fits")) -def test_register(aia_171_map, lvl_15_map): +def test_register(aia_171_map, lvl_15_map) -> None: """ Test that header info for the map has been correctly updated after the map has been scaled to 0.6 arcsec / pixel and aligned with solar north. @@ -50,15 +49,15 @@ def test_register(aia_171_map, lvl_15_map): assert lvl_15_map.meta["lvl_num"] == 1.5 -def test_register_filesave(lvl_15_map): +def test_register_filesave(lvl_15_map, tmp_path) -> None: """ Test that adjusted header values are still correct after saving the map and reloading it. """ - afilename = tempfile.NamedTemporaryFile(suffix=".fits").name + filename = tmp_path / "test_register_filesave.fits" with pytest.warns(VerifyWarning, match="The 'BLANK' keyword is only applicable to integer data"): - lvl_15_map.save(afilename, overwrite=True) - load_map = Map(afilename) + lvl_15_map.save(str(filename), overwrite=True) + load_map = Map(str(filename)) # Check crpix values assert load_map.meta["crpix1"] == lvl_15_map.data.shape[1] / 2.0 + 0.5 assert load_map.meta["crpix2"] == lvl_15_map.data.shape[0] / 2.0 + 0.5 @@ -71,7 +70,7 @@ def test_register_filesave(lvl_15_map): assert load_map.meta["lvl_num"] == 1.5 -def test_register_unsupported_maps(aia_171_map, non_sdo_map): +def test_register_unsupported_maps(aia_171_map, non_sdo_map) -> None: """ Make sure we raise an error when an unsupported map is passed in. """ @@ -84,9 +83,9 @@ def test_register_unsupported_maps(aia_171_map, non_sdo_map): register(non_sdo_map) -def test_register_level_15(lvl_15_map): +def test_register_level_15(lvl_15_map) -> None: with pytest.warns( - AiapyUserWarning, + AIApyUserWarning, match="Image registration should only be applied to level 1 data", ): register(lvl_15_map) @@ -94,7 +93,7 @@ def test_register_level_15(lvl_15_map): # Test case where processing_level is missing and returns None del new_meta["lvl_num"] with pytest.warns( - AiapyUserWarning, + AIApyUserWarning, match="Image registration should only be applied to level 1 data", ): register(lvl_15_map._new_instance(lvl_15_map.data, new_meta)) @@ -110,7 +109,7 @@ def test_register_level_15(lvl_15_map): ), ], ) -def test_correct_degradation(aia_171_map, correction_table, version): +def test_correct_degradation(aia_171_map, correction_table, version) -> None: original_corrected = correct_degradation( aia_171_map, correction_table=correction_table, @@ -162,7 +161,7 @@ def test_correct_degradation(aia_171_map, correction_table, version): ), ], ) -def test_degradation(correction_table, version, time_correction_truth): +def test_degradation(correction_table, version, time_correction_truth) -> None: # NOTE: this just tests an expected result from aiapy, not necessarily an # absolutely correct result. It was calculated for the above time and # the specific correction table file. @@ -224,7 +223,7 @@ def test_degradation(correction_table, version, time_correction_truth): ), ], ) -def test_degradation_all_wavelengths(wavelength, result): +def test_degradation_all_wavelengths(wavelength, result) -> None: obstime = astropy.time.Time("2015-01-01T00:00:00", scale="utc") time_correction = degradation( wavelength * u.angstrom, @@ -234,7 +233,7 @@ def test_degradation_all_wavelengths(wavelength, result): @pytest.mark.remote_data -def test_degradation_4500(): +def test_degradation_4500() -> None: # 4500 has a max version of 3, so by default it will error obstime = astropy.time.Time("2015-01-01T00:00:00", scale="utc") with pytest.raises( @@ -247,7 +246,7 @@ def test_degradation_4500(): assert u.allclose(correction, 1.0 * u.dimensionless_unscaled) -def test_degradation_time_array(): +def test_degradation_time_array() -> None: obstime = astropy.time.Time("2015-01-01T00:00:00", scale="utc") obstime = obstime + np.linspace(0, 1, 100) * u.year correction_table = get_test_filepath("aia_V8_20171210_050627_response_table.txt") @@ -262,7 +261,7 @@ def test_degradation_time_array(): assert tc == degradation(94 * u.angstrom, o, correction_table=correction_table, calibration_version=8) -def test_register_cupy(aia_171_map): +def test_register_cupy(aia_171_map) -> None: pytest.importorskip("cupy") cupy_map = register(aia_171_map, method="cupy") scipy_map = register(aia_171_map, method="scipy") diff --git a/aiapy/calibrate/tests/test_spikes.py b/aiapy/calibrate/tests/test_spikes.py index f98a6b0..fc5cb2d 100644 --- a/aiapy/calibrate/tests/test_spikes.py +++ b/aiapy/calibrate/tests/test_spikes.py @@ -10,7 +10,7 @@ from sunpy.map.mapbase import PixelPair from aiapy.calibrate import fetch_spikes, respike -from aiapy.util import AiapyUserWarning +from aiapy.util import AIApyUserWarning @pytest.fixture @@ -32,26 +32,26 @@ def spikes(despiked_map): @pytest.mark.remote_data -def test_respike(respiked_map, spikes): +def test_respike(respiked_map, spikes) -> None: coords, values = spikes for x, y, v in zip(coords.x.value, coords.y.value, values, strict=True): assert v == respiked_map.data[int(y), int(x)] @pytest.mark.remote_data -def test_respike_meta(respiked_map): +def test_respike_meta(respiked_map) -> None: assert respiked_map.meta["lvl_num"] == 0.5 assert respiked_map.meta["nspikes"] == 0 @pytest.mark.remote_data -def test_fetch_with_prefetched_spikes(despiked_map, respiked_map, spikes): +def test_fetch_with_prefetched_spikes(despiked_map, respiked_map, spikes) -> None: respiked_map_prefetched = respike(despiked_map, spikes=spikes) assert np.allclose(respiked_map.data, respiked_map_prefetched.data) @pytest.mark.remote_data -def test_cutout(respiked_map, despiked_map): +def test_cutout(respiked_map, despiked_map) -> None: blc = (-500, -500) * u.arcsec trc = (500, 500) * u.arcsec respiked_map_cutout = respiked_map.submap( @@ -76,7 +76,7 @@ def test_cutout(respiked_map, despiked_map): ("instrume", "not AIA", TypeError, "Input must be an AIAMap."), ], ) -def test_exceptions(despiked_map, key, value, error, match): +def test_exceptions(despiked_map, key, value, error, match) -> None: new_meta = copy.deepcopy(despiked_map.meta) new_meta[key] = value with pytest.raises(error, match=match): @@ -84,10 +84,10 @@ def test_exceptions(despiked_map, key, value, error, match): @pytest.mark.remote_data -def test_resample_warning(despiked_map): +def test_resample_warning(despiked_map) -> None: despiked_map_resample = despiked_map.resample((512, 512) * u.pixel) with ( - pytest.warns(AiapyUserWarning, match="is significantly different from the expected level 1 plate scale"), + pytest.warns(AIApyUserWarning, match="is significantly different from the expected level 1 plate scale"), pytest.warns(ResourceWarning), ): respike(despiked_map_resample) @@ -95,7 +95,7 @@ def test_resample_warning(despiked_map): @pytest.mark.remote_data @pytest.mark.parametrize(("as_coords", "kind"), [(True, SkyCoord), (False, PixelPair)]) -def test_fetch_spikes(despiked_map, as_coords, kind): +def test_fetch_spikes(despiked_map, as_coords, kind) -> None: n_spikes = despiked_map.meta["nspikes"] coords, values = fetch_spikes(despiked_map, as_coords=as_coords) assert isinstance(coords, kind) diff --git a/aiapy/calibrate/tests/test_uncertainty.py b/aiapy/calibrate/tests/test_uncertainty.py index 7aad989..de9ccb8 100644 --- a/aiapy/calibrate/tests/test_uncertainty.py +++ b/aiapy/calibrate/tests/test_uncertainty.py @@ -17,7 +17,7 @@ @pytest.mark.parametrize("channel", CHANNELS) -def test_error_all_channels(channel): +def test_error_all_channels(channel) -> None: intensity = 10.0 * u.DN / u.pix error = estimate_error(intensity, channel, error_table=table_local) assert error.unit == intensity.unit @@ -33,7 +33,7 @@ def test_error_all_channels(channel): RANDOM_GENERATOR.standard_normal((10, 10, 5)), ], ) -def test_counts_shapes(counts): +def test_counts_shapes(counts) -> None: counts = np.abs(counts) * 1000 * u.DN / u.pix errors = estimate_error(counts, 171 * u.angstrom, error_table=table_local) if counts.shape == (): @@ -57,7 +57,7 @@ def test_counts_shapes(counts): ), ], ) -def test_flags(include_preflight, include_eve, include_chianti, expectation): +def test_flags(include_preflight, include_eve, include_chianti, expectation) -> None: with expectation: errors = estimate_error( 1 * u.DN / u.pix, diff --git a/aiapy/calibrate/tests/test_util.py b/aiapy/calibrate/tests/test_util.py index dd56755..17b7c80 100644 --- a/aiapy/calibrate/tests/test_util.py +++ b/aiapy/calibrate/tests/test_util.py @@ -28,7 +28,7 @@ get_test_filepath("aia_V8_20171210_050627_response_table.txt"), ], ) -def test_correction_table(correction_table): +def test_correction_table(correction_table) -> None: table = get_correction_table(correction_table=correction_table) assert isinstance(table, astropy.table.QTable) expected_columns = [ @@ -48,7 +48,7 @@ def test_correction_table(correction_table): @pytest.mark.parametrize("wavelength", [94 * u.angstrom, 1600 * u.angstrom]) -def test_correction_table_selection(wavelength): +def test_correction_table_selection(wavelength) -> None: table = _select_epoch_from_correction_table(wavelength, obstime, correction_table_local, version=8) assert isinstance(table, astropy.table.QTable) expected_columns = [ @@ -67,7 +67,7 @@ def test_correction_table_selection(wavelength): assert isinstance(table["T_STOP"], astropy.time.Time) -def test_invalid_correction_table_input(): +def test_invalid_correction_table_input() -> None: with pytest.raises( ValueError, match="correction_table must be a file path, an existing table, or None.", @@ -75,24 +75,24 @@ def test_invalid_correction_table_input(): get_correction_table(correction_table=-1) -def test_invalid_wavelength_raises_exception(): +def test_invalid_wavelength_raises_exception() -> None: with pytest.raises(ValueError, match='channel "1800.0 Angstrom" not in list of valid channels'): _select_epoch_from_correction_table(1800 * u.angstrom, obstime, correction_table_local) -def test_wrong_version_number_raises_exception(): +def test_wrong_version_number_raises_exception() -> None: with pytest.raises(ValueError, match="Correction table does not contain calibration for version -1"): _select_epoch_from_correction_table(94 * u.angstrom, obstime, correction_table_local, version=-1) -def test_obstime_out_of_range(): +def test_obstime_out_of_range() -> None: obstime_out_of_range = astropy.time.Time("2000-01-01T12:00:00", scale="utc") with pytest.raises(ValueError, match=f"No valid calibration epoch for {obstime_out_of_range}"): _select_epoch_from_correction_table(94 * u.angstrom, obstime_out_of_range, correction_table_local, version=8) @pytest.mark.remote_data -def test_pointing_table(): +def test_pointing_table() -> None: expected_columns = ["T_START", "T_STOP"] for c in ["094", "171", "193", "211", "304", "335", "1600", "1700", "4500"]: expected_columns += [ @@ -113,7 +113,7 @@ def test_pointing_table(): @pytest.mark.remote_data -def test_pointing_table_unavailable(): +def test_pointing_table_unavailable() -> None: # Check that missing pointing data raises a nice error t = astropy.time.Time("1990-01-01") with pytest.raises(RuntimeError, match="Could not find any pointing information"): @@ -128,12 +128,12 @@ def test_pointing_table_unavailable(): error_table_local, ], ) -def test_error_table(error_table): +def test_error_table(error_table) -> None: table = get_error_table(error_table) assert isinstance(table, astropy.table.QTable) assert len(table) == 10 -def test_invalid_error_table_input(): +def test_invalid_error_table_input() -> None: with pytest.raises(TypeError, match="error_table must be a file path, an existing table, or None"): get_error_table(error_table=-1) diff --git a/aiapy/calibrate/transform.py b/aiapy/calibrate/transform.py index 2ad6b11..d14aafe 100644 --- a/aiapy/calibrate/transform.py +++ b/aiapy/calibrate/transform.py @@ -10,7 +10,7 @@ handles_image_nans=False, handles_nan_missing=True, ) -def _rotation_cupy(image, matrix, shift, order, missing, clip): +def _rotation_cupy(image, matrix, shift, order, missing, clip): # NOQA: ARG001 """ * Rotates using `cupyx.scipy.ndimage.affine_transform` from `cupy `__ * Converts from a numpy array to a cupy array and then back again. diff --git a/aiapy/calibrate/util.py b/aiapy/calibrate/util.py index 3bb2dda..bfb1438 100644 --- a/aiapy/calibrate/util.py +++ b/aiapy/calibrate/util.py @@ -7,7 +7,6 @@ import warnings from urllib.parse import urljoin -import drms import numpy as np from erfa.core import ErfaWarning @@ -16,6 +15,7 @@ from astropy.table import QTable from astropy.time import Time +import drms from sunpy import log from sunpy.net import attrs, jsoc diff --git a/aiapy/conftest.py b/aiapy/conftest.py index 491e375..bc560ec 100644 --- a/aiapy/conftest.py +++ b/aiapy/conftest.py @@ -42,7 +42,7 @@ def psf_94(channels): return aiapy.psf.psf(channels[0], use_preflightcore=True) -def idl_available(): +def idl_available() -> bool | None: try: import hissw diff --git a/aiapy/data/__init__.py b/aiapy/data/__init__.py index e69de29..1038948 100644 --- a/aiapy/data/__init__.py +++ b/aiapy/data/__init__.py @@ -0,0 +1,3 @@ +""" +This module provides sample data, testing data and a data manager. +""" diff --git a/aiapy/data/_manager.py b/aiapy/data/_manager.py index 2b7f7d9..9949f40 100644 --- a/aiapy/data/_manager.py +++ b/aiapy/data/_manager.py @@ -17,7 +17,7 @@ class AIAParfiveDownloader(DownloaderBase): - def download(self, url, path): + def download(self, url, path) -> None: downloader = Downloader() path = Path(path) filename = path.name @@ -37,5 +37,5 @@ def download(self, url, path): AIAParfiveDownloader(), SqliteStorage(f"{_download_dir}/data_manager.db"), _download_dir, - ) + ), ) diff --git a/aiapy/data/_sample.py b/aiapy/data/_sample.py index 1c5b4d1..075c4e5 100644 --- a/aiapy/data/_sample.py +++ b/aiapy/data/_sample.py @@ -65,7 +65,7 @@ def _retry_sample_data(results, new_url_base): return new_results -def _handle_final_errors(results): +def _handle_final_errors(results) -> None: for err in results.errors: file_name = err.url.split("/")[-1] log.debug( diff --git a/aiapy/data/sample.py b/aiapy/data/sample.py index bce218f..e2d223f 100644 --- a/aiapy/data/sample.py +++ b/aiapy/data/sample.py @@ -57,8 +57,8 @@ def __getattr__(name): _SAMPLE_DATA.keys(), _get_sample_files(_SAMPLE_DATA.values(), no_download=True), strict=False, - ) - ) + ), + ), ) if name == "file_list": return [v for v in __getattr__("file_dict").values() if v] @@ -66,7 +66,7 @@ def __getattr__(name): raise AttributeError(msg) -def download_all(*, force_download=False): +def download_all(*, force_download=False) -> None: """ Download all sample data at once that has not already been downloaded. diff --git a/aiapy/psf/__init__.py b/aiapy/psf/__init__.py index 9d8fef7..51d2e88 100644 --- a/aiapy/psf/__init__.py +++ b/aiapy/psf/__init__.py @@ -1,2 +1,7 @@ +""" +This package contains functions for deconvolving AIA images with the instrument +point spread function (PSF). +""" + from .deconvolve import * from .psf import * diff --git a/aiapy/psf/deconvolve.py b/aiapy/psf/deconvolve.py index 224fce6..883489b 100644 --- a/aiapy/psf/deconvolve.py +++ b/aiapy/psf/deconvolve.py @@ -16,7 +16,7 @@ except ImportError: HAS_CUPY = False -from aiapy.util import AiapyUserWarning +from aiapy.util import AIApyUserWarning from .psf import psf as calculate_psf __all__ = ["deconvolve"] @@ -71,7 +71,7 @@ def deconvolve(smap, *, psf=None, iterations=25, clip_negative=True, use_gpu=Tru if np.any(img < 0): warnings.warn( "Image contains negative intensity values. Consider setting clip_negative to True", - AiapyUserWarning, + AIApyUserWarning, stacklevel=3, ) if psf is None: diff --git a/aiapy/psf/tests/test_deconvolve.py b/aiapy/psf/tests/test_deconvolve.py index a8083a8..9f0ffc5 100644 --- a/aiapy/psf/tests/test_deconvolve.py +++ b/aiapy/psf/tests/test_deconvolve.py @@ -5,28 +5,28 @@ import sunpy.map import aiapy.psf -from aiapy.util import AiapyUserWarning +from aiapy.util import AIApyUserWarning -def test_deconvolve(aia_171_map): +def test_deconvolve(aia_171_map) -> None: pytest.importorskip(modname="cupy", reason="Cannot import cupy. Skipping deconvolution test with full PSF") map_decon = aiapy.psf.deconvolve(aia_171_map) assert isinstance(map_decon, sunpy.map.GenericMap) assert map_decon.data.shape == aia_171_map.data.shape -def test_deconvolve_specify_psf(aia_171_map, psf): +def test_deconvolve_specify_psf(aia_171_map, psf) -> None: map_decon = aiapy.psf.deconvolve(aia_171_map, psf=psf, iterations=1) assert isinstance(map_decon, sunpy.map.GenericMap) assert map_decon.data.shape == aia_171_map.data.shape -def test_deconvolve_negative_pixels(aia_171_map, psf): +def test_deconvolve_negative_pixels(aia_171_map, psf) -> None: aia_171_map_neg = aia_171_map._new_instance( np.where(aia_171_map.data < 1, -1, aia_171_map.data), aia_171_map.meta, ) - with pytest.warns(AiapyUserWarning, match="Image contains negative intensity values."): + with pytest.warns(AIApyUserWarning, match="Image contains negative intensity values."): aiapy.psf.deconvolve( aia_171_map_neg, psf=psf, diff --git a/aiapy/psf/tests/test_psf.py b/aiapy/psf/tests/test_psf.py index de3b0dc..7192c5a 100644 --- a/aiapy/psf/tests/test_psf.py +++ b/aiapy/psf/tests/test_psf.py @@ -15,13 +15,13 @@ @pytest.mark.parametrize("use_preflightcore", [True, False]) -def test_filter_mesh_parameters(use_preflightcore, channels): +def test_filter_mesh_parameters(use_preflightcore, channels) -> None: params = aiapy.psf.filter_mesh_parameters(use_preflightcore=use_preflightcore) assert isinstance(params, dict) assert all(c in params for c in channels) assert all(all(p in params[c] for p in MESH_PROPERTIES) for c in channels) -def test_psf(psf): +def test_psf(psf) -> None: assert isinstance(psf, np.ndarray) assert psf.shape == (4096, 4096) diff --git a/aiapy/response/channel.py b/aiapy/response/channel.py index e4b7886..ad1f900 100644 --- a/aiapy/response/channel.py +++ b/aiapy/response/channel.py @@ -123,7 +123,7 @@ def channel( @property def name( self, - ): + ) -> str: return f"{self.channel.to(u.angstrom).value:.0f}" @property diff --git a/aiapy/response/tests/test_channel.py b/aiapy/response/tests/test_channel.py index 8676179..2f82958 100644 --- a/aiapy/response/tests/test_channel.py +++ b/aiapy/response/tests/test_channel.py @@ -16,7 +16,7 @@ # Mark all tests which use this fixture as online @pytest.fixture(params=[pytest.param(None, marks=pytest.mark.remote_data)]) -def channel(request, ssw_home): +def channel(request, ssw_home): # NOQA: ARG001 if ssw_home is not None: instrument_file = Path(ssw_home) / "sdo" / "aia" / "response" / f"aia_V{VERSION_NUMBER}_all_fullinst.genx" else: @@ -58,46 +58,46 @@ def required_keys(): ] -def test_has_instrument_data(channel): +def test_has_instrument_data(channel) -> None: assert hasattr(channel, "_instrument_data") assert isinstance(channel._instrument_data, collections.OrderedDict) -def test_has_channel_data(channel): +def test_has_channel_data(channel) -> None: assert hasattr(channel, "_data") assert isinstance(channel._data, MetaDict) -def test_channel_data_has_keys(channel, required_keys): +def test_channel_data_has_keys(channel, required_keys) -> None: assert all(k in channel._data for k in required_keys) -def test_has_wavelength(channel): +def test_has_wavelength(channel) -> None: assert hasattr(channel, "wavelength") -def test_channel_wavelength(channel): +def test_channel_wavelength(channel) -> None: assert channel.channel == 94 * u.angstrom assert channel.name == "94" -def test_telescope_number(channel): +def test_telescope_number(channel) -> None: assert channel.telescope_number == 4 -def test_invalid_channel(): +def test_invalid_channel() -> None: with pytest.raises(ValueError, match='channel "1.0 Angstrom" not in ' "list of valid channels"): Channel(1 * u.angstrom) -def test_channel_properties(channel, channel_properties): +def test_channel_properties(channel, channel_properties) -> None: # Test that expected properties are present and are quantities. # This does not test correctness for p in channel_properties: assert isinstance(getattr(channel, p), u.Quantity) -def test_effective_area(channel): +def test_effective_area(channel) -> None: effective_area = ( channel.primary_reflectance * channel.secondary_reflectance @@ -137,7 +137,7 @@ def test_effective_area(channel): ), ], ) -def test_eve_correction(channel, correction_table, version, eve_correction_truth): +def test_eve_correction(channel, correction_table, version, eve_correction_truth) -> None: # NOTE: this just tests an expected result from aiapy, not necessarily an # absolutely correct result. It was calculated for the above time and # the correction parameters in JSOC at the time this code was committed/ @@ -154,7 +154,7 @@ def test_eve_correction(channel, correction_table, version, eve_correction_truth assert u.allclose(eve_correction, eve_correction_truth, rtol=1e-10, atol=0.0) -def test_wavelength_response_no_idl(channel): +def test_wavelength_response_no_idl(channel) -> None: # NOTE: this does not test correctness, but just that the method can # be run with the various combinations of inputs. The tests below test the # correctness of the output as evaluated by their similarity to those @@ -176,14 +176,14 @@ def test_wavelength_response_no_idl(channel): ) -def test_wavelength_response_uncorrected(channel, idl_environment): +def test_wavelength_response_uncorrected(channel, idl_environment) -> None: r = channel.wavelength_response() ssw = idl_environment.run("r = aia_get_response(/area,/dn,evenorm=0)", save_vars=["r"], verbose=False) r_ssw = ssw["r"][f"A{channel.name}"][0]["ea"][0] * u.cm**2 * u.DN / u.ph assert u.allclose(r, r_ssw, rtol=1e-4, atol=0.0 * u.cm**2 * u.DN / u.ph) -def test_wavelength_response_no_crosstalk(channel, idl_environment): +def test_wavelength_response_no_crosstalk(channel, idl_environment) -> None: r = channel.wavelength_response(include_crosstalk=False) ssw = idl_environment.run( "r = aia_get_response(/area,/dn,/noblend,evenorm=0)", @@ -195,7 +195,7 @@ def test_wavelength_response_no_crosstalk(channel, idl_environment): @pytest.mark.parametrize("include_eve_correction", [False, True]) -def test_wavelength_response_time(channel, idl_environment, include_eve_correction): +def test_wavelength_response_time(channel, idl_environment, include_eve_correction) -> None: now = astropy.time.Time.now() correction_table = get_test_filepath("aia_V8_20171210_050627_response_table.txt") calibration_version = 8 @@ -225,7 +225,7 @@ def test_wavelength_response_time(channel, idl_environment, include_eve_correcti @pytest.mark.remote_data @pytest.mark.parametrize("channel_wavelength", [1600 * u.angstrom, 1700 * u.angstrom, 4500 * u.angstrom]) -def test_fuv_channel(channel_wavelength, channel_properties, required_keys): +def test_fuv_channel(channel_wavelength, channel_properties, required_keys) -> None: # There are a few corner cases for the 1600, 1700, and 4500 channels channel = Channel(channel_wavelength) assert all(k in channel._data for k in required_keys) diff --git a/aiapy/tests/test_idl.py b/aiapy/tests/test_idl.py index d36b2c7..63b5a5f 100644 --- a/aiapy/tests/test_idl.py +++ b/aiapy/tests/test_idl.py @@ -21,7 +21,7 @@ [171 * u.angstrom, 1000 * u.DN / u.pix, False, False, True], ], ) -def test_error_consistent(idl_environment, channel, counts, include_eve, include_preflight, include_chianti): +def test_error_consistent(idl_environment, channel, counts, include_eve, include_preflight, include_chianti) -> None: idl = """ common aia_bp_error_common,common_errtable common_errtable=aia_bp_read_error_table('{{ error_table }}') @@ -73,7 +73,7 @@ def psf_idl(idl_environment, request): return r["psf"] -def test_psf_consistent(psf_94, psf_idl): +def test_psf_consistent(psf_94, psf_idl) -> None: """ Check whether PSF is consistent with IDL calculation. diff --git a/aiapy/tests/test_init.py b/aiapy/tests/test_init.py index 8e02821..c0fc13a 100644 --- a/aiapy/tests/test_init.py +++ b/aiapy/tests/test_init.py @@ -1,5 +1,5 @@ import aiapy -def test_citation(): +def test_citation() -> None: assert aiapy.__citation__ not in [None, ""] diff --git a/aiapy/util/decorators.py b/aiapy/util/decorators.py index 1574472..0b03abf 100644 --- a/aiapy/util/decorators.py +++ b/aiapy/util/decorators.py @@ -1,3 +1,8 @@ +""" +This module contains decorators for validating arguments related to AIA +channels. +""" + import inspect import functools diff --git a/aiapy/util/exceptions.py b/aiapy/util/exceptions.py index 3bb3475..7aa18e6 100644 --- a/aiapy/util/exceptions.py +++ b/aiapy/util/exceptions.py @@ -4,19 +4,19 @@ from astropy.utils.exceptions import AstropyWarning -__all__ = ["AiapyUserWarning", "AiapyWarning"] +__all__ = ["AIApyUserWarning", "AIApyWarning"] -class AiapyWarning(AstropyWarning): +class AIApyWarning(AstropyWarning): """ The base warning class from which all aiapy warnings should inherit. This warning should not be issued in normal code. Use - "AiapyUserWarning" instead or a specific sub-class. + "AIApyUserWarning" instead or a specific sub-class. """ -class AiapyUserWarning(UserWarning, AiapyWarning): +class AIApyUserWarning(UserWarning, AIApyWarning): """ The primary warning class for aiapy. diff --git a/aiapy/util/tests/test_util.py b/aiapy/util/tests/test_util.py index 6189184..6a9df80 100644 --- a/aiapy/util/tests/test_util.py +++ b/aiapy/util/tests/test_util.py @@ -6,7 +6,7 @@ @pytest.mark.remote_data -def test_sdo_location(aia_171_map): +def test_sdo_location(aia_171_map) -> None: # Confirm that the queried location matches AIAMap's interpretation of the FITS file result = aiapy.util.sdo_location(aia_171_map.date) aia_171_map.observer_coordinate.transform_to(result) @@ -14,7 +14,7 @@ def test_sdo_location(aia_171_map): @pytest.mark.remote_data -def test_sdo_location_raises_error(): +def test_sdo_location_raises_error() -> None: # Confirm that an error is raised for a time without records with pytest.raises(ValueError, match="No DRMS records near this time"): aiapy.util.sdo_location("2001-01-01") diff --git a/aiapy/util/util.py b/aiapy/util/util.py index afde9e1..457ef48 100644 --- a/aiapy/util/util.py +++ b/aiapy/util/util.py @@ -2,13 +2,13 @@ Miscellaneous utility functions. """ -import drms import numpy as np import astropy.units as u from astropy.coordinates import SkyCoord from astropy.time import Time +import drms from sunpy.time import parse_time from aiapy.util.decorators import validate_channel diff --git a/aiapy/version.py b/aiapy/version.py index 515c2f0..da25a91 100644 --- a/aiapy/version.py +++ b/aiapy/version.py @@ -6,12 +6,13 @@ from ._dev.scm_version import version except ImportError: from ._version import version -except Exception: +except Exception: # NOQA: BLE001 import warnings warnings.warn( - f'could not determine {__name__.split(".")[0]} package version; this indicates a broken installation' + f'could not determine {__name__.split(".")[0]} package version; this indicates a broken installation', + stacklevel=2, ) del warnings - version = '0.0.0' + version = "0.0.0" diff --git a/docs/conf.py b/docs/conf.py index 0b8525f..9fefced 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,9 +7,10 @@ import datetime import os import warnings -from packaging.version import Version from pathlib import Path +from packaging.version import Version + # -- Read the Docs Specific Configuration -------------------------------------- # This needs to be done before aiapy/sunpy is imported @@ -68,7 +69,6 @@ "sphinx_automodapi.automodapi", "sphinx_automodapi.smart_resolver", "sphinx_changelog", - "sunpy.util.sphinx.doctest", "sunpy.util.sphinx.generate", "sphinxext.opengraph", "sphinx_design", diff --git a/docs/reference/util.rst b/docs/reference/util.rst index ccaa2f4..732df82 100644 --- a/docs/reference/util.rst +++ b/docs/reference/util.rst @@ -3,5 +3,5 @@ Utilities (``aiapy.util``) ========================== .. automodapi:: aiapy.util - :skip: AiapyWarning, AiapyUserWarning + :skip: AIApyWarning, AIApyUserWarning :no-inheritance-diagram: diff --git a/ruff.toml b/ruff.toml index aa61dd0..e21c779 100644 --- a/ruff.toml +++ b/ruff.toml @@ -7,10 +7,11 @@ extend-exclude=[ "build", "tools/**", ] -lint.select = [ +lint.extend-select = [ "ALL", ] lint.extend-ignore = [ + "I", # Ignore isort "ANN001", # Missing type annotation for function argument "ANN002", # Missing type annotation for variable "ANN003", # Missing type annotation for keyword @@ -25,10 +26,13 @@ lint.extend-ignore = [ "D401", # First line should be in imperative mood "D404", # First word of the docstring should not be "This" "E501", # Line too long + "F403", # unable to detect undefined names, "FIX001", # Line contains FIXME, consider resolving the issue "FIX002", # Line contains TODO, consider resolving the issue "ISC001", # May cause conflicts when used with the formatter "PLR0913", # Too many arguments in function definition + "PLR2004", # Magic value used in comparison + "SLF001", # Private member accessed "TD001", # Invalid TODO tag: `FIXME` "TD002", # Missing author in TODO "TD003", # Missing issue link on the line following this TODO @@ -45,6 +49,8 @@ lint.extend-ignore = [ "docs/conf.py" = [ "D100", # Missing docstring in public module "INP001", # conf.py is part of an implicit namespace package + "E402", # Module level import not at top of file + "ERA001", # Found commented-out code ] "setup.py" = [ "D100", # Missing docstring in public module @@ -61,7 +67,6 @@ lint.extend-ignore = [ "D100", # Missing docstring in public module "D103", # Missing docstring in public function "N806", # in function should be lowercase - "PLR2004", # Magic value used in comparison "S101", # Use of `assert` detected ] "*version.py" = [ @@ -77,5 +82,6 @@ convention = "numpy" [format] docstring-code-format = true +docstring-code-line-length = 80 indent-style = "space" quote-style = "double"