Skip to content

Commit

Permalink
ENH: Add tests for AWS access functionality
Browse files Browse the repository at this point in the history
This requires some infrastructure to use VCRPy to record and mock out
web traffic, as well as an optional dependency on boto3.
  • Loading branch information
dopplershift committed Jan 10, 2025
1 parent add3781 commit 4e8d232
Show file tree
Hide file tree
Showing 12 changed files with 6,984 additions and 4 deletions.
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ staticdata/*.tbl text eol=lf
*.ipynb diff=jupyternotebook
talks/* linguist-documentation
src/metpy/io/_metar_parser/metar_parser.py linguist-generated=true
src/metpy/_vendor/* linguist-vendored
src/metpy/_vendor/* linguist-vendored

# Mark the vcrpy cassettes as generated
tests/remote/fixtures/* linguist-generated=true
3 changes: 3 additions & 0 deletions .github/workflows/tests-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ jobs:
# Only needed while we support numpy 1.20
if fname == 'requirements.txt':
out.write('pillow!=10.4.0\n')
# Needed until minium vcrpy is >=7.0.0 for urllib3>=2.3
elif fname == 'requirements.txt':
out.write('urllib3==2.2.3\n')
EOF
- name: Install from PyPI
Expand Down
1 change: 1 addition & 0 deletions ci/extra_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
cartopy==0.24.0
dask==2024.12.1
shapely==2.0.6
boto3==1.35.88
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ test = [
"netCDF4",
"packaging>=21.0",
"pytest>=7.0",
"pytest-mpl"
"pytest-mpl",
"vcrpy>=4.3.1"
]
extras = [
"cartopy>=0.21.0",
"dask>=2020.12.0",
"shapely>=1.6.4"
"shapely>=1.6.4",
"boto3>=1.26.45"
]

[project.urls]
Expand All @@ -77,7 +79,7 @@ extras = [
"MetPy Mondays" = "https://www.youtube.com/playlist?list=PLQut5OXpV-0ir4IdllSt1iEZKTwFBa7kO"

[tool.codespell]
skip = "*.tbl,*.ipynb,AUTHORS.txt,gempak.rst,.git,./staticdata,./docs/build,*.pdf,./talks"
skip = "*.tbl,*.ipynb,AUTHORS.txt,gempak.rst,.git,./staticdata,./docs/build,*.pdf,./talks,./tests/remote/fixtures"
exclude-file = ".codespellexclude"
ignore-words = ".codespellignore"

Expand Down
19 changes: 19 additions & 0 deletions src/metpy/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import contextlib
import functools
from importlib.metadata import PackageNotFoundError, requires, version
import inspect
import operator as op
from pathlib import Path
import re

import matplotlib.pyplot as plt
Expand Down Expand Up @@ -132,6 +134,23 @@ def wrapped(*args, **kwargs):
needs_cartopy = needs_module('cartopy')


def needs_aws(test_func):
"""Decorate a test function that needs AWS functionality.
This both sets up recording using VCRPy as well as ensures that the the appropriate
AWS libraries are installed, otherwise the test is skipped.
"""
# Get the vcr module this way so we can skip tests if it's not present
vcr = pytest.importorskip('vcr')

# Set up the fixtures relative to the test file
func_path = inspect.getfile(test_func)
fixture_path = Path(func_path).with_name('fixtures') / f'{test_func.__name__}.yaml'

# Set the cassette to use and also a wrapper function that skips the test if no boto3
return vcr.use_cassette(fixture_path)(needs_module('boto3')(test_func))


@contextlib.contextmanager
def autoclose_figure(*args, **kwargs):
"""Create a figure that is automatically closed when exiting a block.
Expand Down
158 changes: 158 additions & 0 deletions tests/remote/fixtures/test_goes_range.yaml

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions tests/remote/fixtures/test_goes_single.yaml

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions tests/remote/fixtures/test_nexrad2_range.yaml

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions tests/remote/fixtures/test_nexrad2_single.yaml

Large diffs are not rendered by default.

5,767 changes: 5,767 additions & 0 deletions tests/remote/fixtures/test_nexrad3_range.yaml

Large diffs are not rendered by default.

771 changes: 771 additions & 0 deletions tests/remote/fixtures/test_nexrad3_single.yaml

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions tests/remote/test_aws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright (c) 2025 MetPy Developers.
# Distributed under the terms of the BSD 3-Clause License.
# SPDX-License-Identifier: BSD-3-Clause
"""Test the `metpy.remote.aws` module."""
from datetime import datetime
from pathlib import Path
import tempfile

from metpy.remote import GOESArchive, NEXRADLevel2Archive, NEXRADLevel3Archive
from metpy.testing import needs_aws


@needs_aws
def test_nexrad3_single():
"""Test getting a single product from the NEXRAD level 3 archive."""
l3 = NEXRADLevel3Archive().get_product('FTG', 'N0Q', datetime(2020, 4, 1, 12, 30))
assert l3.path == 'FTG_N0Q_2020_04_01_12_30_12'
assert l3.parse()


@needs_aws
def test_nexrad3_range():
"""Test getting a range of products from the NEXRAD level 3 archive."""
prods = list(NEXRADLevel3Archive().get_range('FTG', 'N0B', datetime(2024, 12, 31, 23, 45),
datetime(2025, 1, 1, 1, 15)))
names = [p.name for p in prods]
assert names == ['FTG_N0B_2024_12_31_23_46_08', 'FTG_N0B_2024_12_31_23_53_12',
'FTG_N0B_2025_01_01_00_00_17', 'FTG_N0B_2025_01_01_00_07_22',
'FTG_N0B_2025_01_01_00_14_26', 'FTG_N0B_2025_01_01_00_21_30',
'FTG_N0B_2025_01_01_00_28_34', 'FTG_N0B_2025_01_01_00_35_38',
'FTG_N0B_2025_01_01_00_42_41', 'FTG_N0B_2025_01_01_00_49_45',
'FTG_N0B_2025_01_01_00_56_49', 'FTG_N0B_2025_01_01_01_03_54',
'FTG_N0B_2025_01_01_01_10_58']
with tempfile.TemporaryDirectory() as tmpdir:
prod = prods[2]
prod.download(tmpdir)
assert (Path(tmpdir) / 'FTG_N0B_2025_01_01_00_00_17').exists()

prods[4].download(Path(tmpdir) / 'tempprod')
assert (Path(tmpdir) / 'tempprod').exists()


@needs_aws
def test_nexrad2_single():
"""Test getting a single volume from the NEXRAD level 2 archive."""
l2 = NEXRADLevel2Archive().get_product('KTLX', datetime(2013, 5, 20, 20, 15))
assert l2.name == 'KTLX20130520_201643_V06.gz'


@needs_aws
def test_nexrad2_range():
"""Test getting a range of products from the NEXRAD level 2 archive."""
vols = list(NEXRADLevel2Archive().get_range('KFTG', datetime(2024, 12, 14, 15, 15),
datetime(2024, 12, 14, 16, 25)))
names = [v.name for v in vols]
assert names == ['KFTG20241214_151956_V06', 'KFTG20241214_152855_V06',
'KFTG20241214_153754_V06', 'KFTG20241214_154653_V06',
'KFTG20241214_155552_V06', 'KFTG20241214_160451_V06',
'KFTG20241214_161349_V06', 'KFTG20241214_162248_V06']


@needs_aws
def test_goes_single():
"""Test getting a single product from the GOES archive."""
prod = GOESArchive(18).get_product('ABI-L1b-RadM1', datetime(2025, 1, 9, 23, 56), band=2)
assert prod.url == ('https://noaa-goes18.s3.amazonaws.com/ABI-L1b-RadM/2025/009/23/'
'OR_ABI-L1b-RadM1-M6C02_G18_s20250092356254_e20250092356311_'
'c20250092356338.nc')
assert prod.parse().attrs['dataset_name'] == ('OR_ABI-L1b-RadM1-M6C02_G18_s20250092356254_'
'e20250092356311_c20250092356338.nc')


@needs_aws
def test_goes_range():
"""Test getting a range of products from the GOES archive."""
prods = list(GOESArchive(16).get_range('ABI-L1b-RadC', datetime(2024, 12, 10, 1, 0),
datetime(2024, 12, 10, 2, 15), band=1))
names = [p.name for p in prods]
truth = ['OR_ABI-L1b-RadC-M6C01_G16_s20243450101170_e20243450103543_c20243450103590.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450106170_e20243450108543_c20243450108594.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450111170_e20243450113543_c20243450113586.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450116170_e20243450118543_c20243450118589.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450121170_e20243450123543_c20243450123594.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450126170_e20243450128543_c20243450129025.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450131170_e20243450133543_c20243450133590.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450136170_e20243450138543_c20243450138585.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450141170_e20243450143543_c20243450143595.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450146170_e20243450148543_c20243450148579.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450151170_e20243450153543_c20243450154026.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450156170_e20243450158543_c20243450158585.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450201170_e20243450203543_c20243450204022.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450206170_e20243450208543_c20243450208597.nc',
'OR_ABI-L1b-RadC-M6C01_G16_s20243450211170_e20243450213543_c20243450214031.nc']
assert names == truth

0 comments on commit 4e8d232

Please sign in to comment.