diff --git a/README.md b/README.md index 62fc10a..40b1d49 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,20 @@ $ ialirt-data-access --url ialirt-log-query --year --doy --in ### Query / Search for packets -Find all files from a given year, day of year, hour, minute, and second. +Two mutually exclusive query modes are supported. + +**Individual params mode** — query by partial datetime (year and doy required): ```bash $ ialirt-data-access --url ialirt-packet-query --year --doy [--hh ] [--mm ] [--ss ] ``` +**UTC range mode** — query by ISO 8601 time range (time_utc_start required): + +```bash +$ ialirt-data-access --url ialirt-packet-query --time_utc_start [--time_utc_end ] +``` + ### Query / Search for archive CDF files Find all archive CDF files, optionally filtered by year, month, and day. All parameters are optional; version defaults to 1. diff --git a/ialirt_data_access/cli.py b/ialirt_data_access/cli.py index 6557143..501c24a 100644 --- a/ialirt_data_access/cli.py +++ b/ialirt_data_access/cli.py @@ -9,6 +9,9 @@ ialirt-data-access --url ialirt-packet-query --year 2025 --doy 148 --hh 16 --mm 24 + ialirt-data-access --url ialirt-packet-query + --time_utc_start 2025-10-29T18:55:02 --time_utc_end 2025-10-29T19:05:00 + ialirt-data-access --url ialirt-archive-query --year 2024 --month 05 --day 21 --version 1 @@ -79,7 +82,8 @@ def _packet_query_parser(args: argparse.Namespace): Parameters ---------- args : argparse.Namespace - Parsed arguments including year, doy, hh, mm, ss. + Parsed arguments including year, doy, hh, mm, ss, + time_utc_start, time_utc_end. Returns ------- @@ -91,6 +95,8 @@ def _packet_query_parser(args: argparse.Namespace): "hh": args.hh, "mm": args.mm, "ss": args.ss, + "time_utc_start": args.time_utc_start, + "time_utc_end": args.time_utc_end, } # Remove any keys with None values. query_params = {k: v for k, v in query_params.items() if v is not None} @@ -179,7 +185,7 @@ def _data_product_query_parser(args: argparse.Namespace): print(f"Error: {e}") -def main(): +def main(): # noqa: PLR0915 """Parse the command line arguments. Run the command line interface to the I-ALiRT Data Access API. @@ -246,10 +252,17 @@ def main(): # Packet query command packet_query_parser = subparsers.add_parser("ialirt-packet-query") packet_query_parser.add_argument( - "--year", type=str, required=True, help="Year of the packet (e.g., 2025)." + "--year", + type=str, + required=False, + help="Year of the packet (e.g., 2025). Required for individual params mode.", ) packet_query_parser.add_argument( - "--doy", type=str, required=True, help="Day of year of the packet (e.g., 148)." + "--doy", + type=str, + required=False, + help="Day of year of the packet (e.g., 148). " + "Required for individual params mode.", ) packet_query_parser.add_argument( "--hh", type=str, required=False, help="Hour (0 to 23)." @@ -260,6 +273,20 @@ def main(): packet_query_parser.add_argument( "--ss", type=str, required=False, help="Second (0 to 59)." ) + packet_query_parser.add_argument( + "--time_utc_start", + type=str, + required=False, + help="Start of UTC time range (ISO 8601, e.g., 2025-10-29T18:55:02). " + "Required for UTC range mode.", + ) + packet_query_parser.add_argument( + "--time_utc_end", + type=str, + required=False, + help="End of UTC time range (ISO 8601, e.g., 2025-10-29T19:05:00). " + "Optional in UTC range mode.", + ) packet_query_parser.set_defaults(func=_packet_query_parser) # Archive query command diff --git a/ialirt_data_access/io.py b/ialirt_data_access/io.py index 1e7a1fe..dcfe444 100644 --- a/ialirt_data_access/io.py +++ b/ialirt_data_access/io.py @@ -132,44 +132,72 @@ def log_query(*, year: str, doy: str, instance: str) -> list[str]: return items -def packet_query( +def packet_query( # noqa: PLR0913 *, - year: str, - doy: str, + year: Optional[str] = None, + doy: Optional[str] = None, hh: Optional[str] = None, mm: Optional[str] = None, ss: Optional[str] = None, + time_utc_start: Optional[str] = None, + time_utc_end: Optional[str] = None, ) -> list[str]: - """Query the I-ALiRT packet files by partial datetime. + """Query the I-ALiRT packet files. + + Two mutually exclusive query modes are supported: + + UTC range mode — strict ISO 8601 format ``YYYY-MM-DDTHH:MM:SS``. + ``time_utc_start`` is required; ``time_utc_end`` is optional. + + Individual params mode — partial time specification + (``year``, ``doy``[, ``hh``[, ``mm``[, ``ss``]]]). + At minimum, ``year`` and ``doy`` must be provided. Parameters ---------- - year : str - Year, e.g., '2025' - doy : str - Day of year, e.g., '148' + year : str, optional + Year, e.g., '2025'. Required for individual params mode. + doy : str, optional + Day of year, e.g., '148'. Required for individual params mode. hh : str, optional - Hour of day, 0 to 23 + Hour of day, 0 to 23. mm : str, optional - Minute, 0 to 59 + Minute, 0 to 59. ss : str, optional - Second, 0 to 59 + Second, 0 to 59. + time_utc_start : str, optional + Start of UTC time range (ISO 8601, e.g., '2025-10-29T18:55:02'). + Required for UTC range mode. + time_utc_end : str, optional + End of UTC time range (ISO 8601). Optional in UTC range mode. Returns ------- list of str Matching packet file names. """ - query_params = {"year": year, "doy": doy} - - if hh: - query_params["hh"] = hh - if mm: - query_params["mm"] = mm - if ss: - query_params["ss"] = ss + utc_params = { + k: v + for k, v in { + "time_utc_start": time_utc_start, + "time_utc_end": time_utc_end, + }.items() + if v is not None + } - _validate_query_params(**query_params) + if utc_params: + query_params: dict = {"time_utc_start": time_utc_start} + if time_utc_end: + query_params["time_utc_end"] = time_utc_end + else: + query_params = {"year": year, "doy": doy} + if hh: + query_params["hh"] = hh + if mm: + query_params["mm"] = mm + if ss: + query_params["ss"] = ss + _validate_query_params(**query_params) url = f"{ialirt_data_access.config['DATA_ACCESS_URL']}" url += f"/ialirt-packet-query?{urlencode(query_params)}" diff --git a/tests/test_io.py b/tests/test_io.py index 3046c34..f30f3c4 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -87,6 +87,44 @@ def test_packet_query(mock_urlopen: unittest.mock.MagicMock): assert called_url == expected_url_encoded +def test_packet_query_utc_range(mock_urlopen: unittest.mock.MagicMock): + """Test packet_query in UTC range mode.""" + filename = "iois_1_packets_2025_302_18_55_02" + _set_mock_data(mock_urlopen, json.dumps([filename]).encode("utf-8")) + + response = ialirt_data_access.packet_query( + time_utc_start="2025-10-29T18:55:02", + time_utc_end="2025-10-29T19:05:00", + ) + assert response == [filename] + + mock_urlopen.assert_called_once() + urlopen_call = mock_urlopen.mock_calls[0].args[0] + called_url = urlopen_call.full_url + expected_url_encoded = "https://ialirt.test.com/ialirt-packet-query?" + urlencode( + { + "time_utc_start": "2025-10-29T18:55:02", + "time_utc_end": "2025-10-29T19:05:00", + } + ) + assert called_url == expected_url_encoded + + +def test_packet_query_utc_start_only(mock_urlopen: unittest.mock.MagicMock): + """Test packet_query in UTC range mode with only time_utc_start.""" + filename = "iois_1_packets_2025_302_18_55_02" + _set_mock_data(mock_urlopen, json.dumps([filename]).encode("utf-8")) + + response = ialirt_data_access.packet_query(time_utc_start="2025-10-29T18:55:02") + assert response == [filename] + + urlopen_call = mock_urlopen.mock_calls[0].args[0] + expected_url_encoded = "https://ialirt.test.com/ialirt-packet-query?" + urlencode( + {"time_utc_start": "2025-10-29T18:55:02"} + ) + assert urlopen_call.full_url == expected_url_encoded + + def test_query_bad_params(mock_urlopen: unittest.mock.MagicMock): """Test a call to the Query API that has invalid parameters.""" with pytest.raises(