Skip to content

Commit a471aad

Browse files
authored
ENH: Adding Release command to IMAP CLI (#316)
1 parent a28c616 commit a471aad

4 files changed

Lines changed: 258 additions & 2 deletions

File tree

imap_data_access/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
ScienceFilePath,
2020
SPICEFilePath,
2121
)
22-
from imap_data_access.io import download, query, reprocess, spice_query, upload
22+
from imap_data_access.io import download, query, release, reprocess, spice_query, upload
2323
from imap_data_access.processing_input import (
2424
AncillaryInput,
2525
ProcessingInputCollection,
@@ -48,6 +48,7 @@
4848
"SpinInput",
4949
"download",
5050
"query",
51+
"release",
5152
"reprocess",
5253
"spice_query",
5354
"upload",

imap_data_access/cli.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
ScienceFilePath,
3030
generate_imap_file_path,
3131
)
32-
from imap_data_access.io import query, spice_query
32+
from imap_data_access.io import query, release, spice_query
33+
from imap_data_access.utils import ReleaseType
3334
from imap_data_access.webpoda import download_daily_data
3435

3536

@@ -339,6 +340,27 @@ def _webpoda_parser(args: argparse.Namespace):
339340
print("Successfully downloaded the data from webpoda.")
340341

341342

343+
def _release_parser(args: argparse.Namespace):
344+
"""Submit a release request to the IMAP SDC.
345+
346+
Parameters
347+
----------
348+
args : argparse.Namespace
349+
An object containing the parsed arguments and their values
350+
"""
351+
# All validation is now handled in io.py
352+
release(
353+
instrument=args.instrument,
354+
release_type=args.release_type,
355+
start_date=args.start_date,
356+
end_date=args.end_date,
357+
release_number=args.release_number,
358+
exclude_file=args.exclude_file,
359+
manifest_file=args.manifest_file,
360+
)
361+
print("Successfully submitted release request to the IMAP SDC.")
362+
363+
342364
def add_query_args(subparser: ArgumentParser) -> None:
343365
"""Add query arguments to subparser.
344366
@@ -662,6 +684,98 @@ def main():
662684
help="Descriptor of the product to reprocess (raw, burst, etc.)",
663685
)
664686
reprocess_parser.set_defaults(func=_reprocess_parser)
687+
688+
# Release command
689+
release_help = (
690+
"Make a release or unrelease file for IMAP data with specified release type. "
691+
"Run 'release -h' for more information."
692+
)
693+
694+
parser_release = subparsers.add_parser(
695+
"release",
696+
help=release_help,
697+
formatter_class=argparse.RawTextHelpFormatter,
698+
)
699+
parser_release.add_argument(
700+
"--instrument",
701+
type=str,
702+
required=True,
703+
metavar="INSTRUMENT",
704+
help="Name of the instrument (e.g., mag, swe, lo, codice)",
705+
choices=imap_data_access.VALID_INSTRUMENTS,
706+
)
707+
parser_release.add_argument(
708+
"--start-date",
709+
type=str,
710+
required=True,
711+
metavar="YYYYMMDD",
712+
help="Start date for the release",
713+
)
714+
parser_release.add_argument(
715+
"--end-date",
716+
type=str,
717+
required=True,
718+
metavar="YYYYMMDD",
719+
help="End date for the release",
720+
)
721+
parser_release.add_argument(
722+
"--release-type",
723+
type=str,
724+
required=True,
725+
metavar="ReleaseType",
726+
help=(
727+
"Type of release:\n"
728+
"- 'release': IMAP mission-wide public release. By default, all\n"
729+
" files are released unless specified in the --exclude-file to\n"
730+
" be withheld.\n"
731+
"- 'early-release': Early release of selected files approved by\n"
732+
" both instrument and project. Use --manifest-file to specify\n"
733+
" files to release early.\n"
734+
"- 'unrelease': Unrelease previously released files due to\n"
735+
" various causes and reasons. Use --manifest-file to specify\n"
736+
" files to unrelease."
737+
),
738+
choices=[e.value for e in ReleaseType],
739+
)
740+
parser_release.add_argument(
741+
"--release-number",
742+
type=int,
743+
required=False,
744+
metavar="NUMBER",
745+
help="Release number (required only when --release-type is 'release'). ",
746+
)
747+
parser_release.add_argument(
748+
"--exclude-file",
749+
type=str,
750+
required=False,
751+
metavar="PATH",
752+
default=None,
753+
help=(
754+
"Path to a file listing files to exclude from public release.\n"
755+
"Used for 'release' type to specify files to withhold.\n"
756+
"File name should follow: \n imap_<instrument>_withhold-data-"
757+
"release-<###>_<start_date>_<end_date>_<version>.txt\n"
758+
),
759+
)
760+
parser_release.add_argument(
761+
"--manifest-file",
762+
type=str,
763+
required=False,
764+
metavar="PATH",
765+
default=None,
766+
help=(
767+
"Path to a file listing files to apply action to in 'early-release'"
768+
" or\n 'unrelease' types. This file serves as the manifest for files\n"
769+
"to be released early or unreleased.\n"
770+
"File name should follow:\n"
771+
" - early-release: "
772+
"imap_<instrument>_early-release_<start_date>_<end_date>_<version>.txt\n"
773+
" - unrelease: "
774+
"imap_<instrument>_unrelease_<start_date>_<end_date>_<version>.txt\n"
775+
),
776+
)
777+
parser_release.set_defaults(func=_release_parser)
778+
665779
# Parse the arguments and set the values
666780
try:
667781
args = parser.parse_args()

imap_data_access/io.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import contextlib
44
import logging
5+
import os
56
from pathlib import Path
67
from typing import Optional, Union
78

@@ -14,6 +15,7 @@
1415
ScienceFilePath,
1516
generate_imap_file_path,
1617
)
18+
from imap_data_access.utils import ReleaseType
1719

1820
logger = logging.getLogger(__name__)
1921

@@ -529,3 +531,131 @@ def upload(file_path: Union[Path, str]) -> None:
529531
)
530532

531533
logger.info("File %s uploaded successfully", file_path)
534+
535+
536+
def release(
537+
*,
538+
instrument: str,
539+
release_type: str,
540+
start_date: str,
541+
end_date: str,
542+
release_number: Optional[int] = None,
543+
exclude_file: Optional[Union[Path, str]] = None,
544+
manifest_file: Optional[Union[Path, str]] = None,
545+
) -> None:
546+
"""Submit a release file to the data archive API.
547+
548+
Parameters
549+
----------
550+
instrument : str
551+
Instrument name (e.g., ``mag``, ``swe``)
552+
release_type : str
553+
Type of release:
554+
- 'release': IMAP mission-wide public release. By default, all files
555+
are released unless specified in the exception list to be withheld.
556+
- 'early-release': Early release of selected files approved by both
557+
instrument and project.
558+
- 'unrelease': Unrelease previously released files due to various
559+
causes and reasons.
560+
start_date : str
561+
Start date in YYYYMMDD format
562+
end_date : str
563+
End date in YYYYMMDD format
564+
release_number : int, optional
565+
Release number. Defaults to ``None``. Required if release_type is
566+
'release' and should be an integer value.
567+
exclude_file : str, optional
568+
Path to exclude file containing list of files to exclude from public release.
569+
manifest_file : str, optional
570+
Path to manifest file containing list of files to apply action to in
571+
'early-release' or 'unrelease' types.
572+
573+
Raises
574+
------
575+
ValueError
576+
If API key is not configured or if any of the required parameters are invalid
577+
IMAPDataAccessError
578+
If the API request fails
579+
"""
580+
# Check for API key - required for release operations
581+
if not imap_data_access.config["API_KEY"]:
582+
raise ValueError(
583+
"API key is required for release operations. "
584+
"Set the IMAP_API_KEY environment variable or use --api-key argument."
585+
)
586+
587+
# Validate instrument
588+
if instrument not in imap_data_access.VALID_INSTRUMENTS:
589+
raise ValueError(
590+
"Not a valid instrument, please choose from "
591+
+ ", ".join(imap_data_access.VALID_INSTRUMENTS)
592+
)
593+
594+
# Validate release_type
595+
valid_release_types = [e.value for e in ReleaseType]
596+
if release_type not in valid_release_types:
597+
raise ValueError(
598+
f"Not a valid release type, please choose from {valid_release_types}"
599+
)
600+
601+
# Validate release_type == "release" requires release_number
602+
if release_type == ReleaseType.RELEASE.value and release_number is None:
603+
raise ValueError(
604+
"The 'release_number' parameter is required for 'release' release type."
605+
)
606+
607+
# Validate start_date
608+
if not file_validation.ImapFilePath.is_valid_date(start_date):
609+
raise ValueError("Not a valid start date, use format 'YYYYMMDD'.")
610+
611+
# Validate end_date
612+
if not file_validation.ImapFilePath.is_valid_date(end_date):
613+
raise ValueError("Not a valid end date, use format 'YYYYMMDD'.")
614+
615+
# Handle exclude file upload if provided
616+
if exclude_file is not None:
617+
# Upload the exclude file using the standard upload function
618+
upload(exclude_file)
619+
logger.info("Exclude file uploaded successfully")
620+
621+
# Handle manifest file upload if provided
622+
if manifest_file is not None:
623+
# Upload the manifest file using the standard upload function
624+
upload(manifest_file)
625+
logger.info("Manifest file uploaded successfully")
626+
627+
# Build release parameters
628+
release_params = {
629+
"instrument": instrument,
630+
"release_type": release_type,
631+
"start_date": start_date,
632+
"end_date": end_date,
633+
}
634+
635+
# Add release_number only if release_type is 'release'
636+
if release_type == ReleaseType.RELEASE.value:
637+
release_params["release_number"] = release_number
638+
639+
# Add optional parameters if provided
640+
if exclude_file is not None:
641+
# API only needs the filename, not the full path
642+
release_params["exclude_file"] = os.path.basename(exclude_file)
643+
644+
if manifest_file is not None:
645+
# API only needs the filename, not the full path
646+
release_params["manifest_file"] = os.path.basename(manifest_file)
647+
648+
logger.debug("Input release parameters: %s", release_params)
649+
650+
url = f"{_get_base_url()}/release"
651+
request = requests.Request(method="GET", url=url, params=release_params).prepare()
652+
653+
logger.info("Submitting release request to %s with params %s", url, release_params)
654+
with _make_request(request) as response:
655+
result = response.json()
656+
logger.debug("Received JSON: %s", result)
657+
658+
logger.info(
659+
f"Release request submitted successfully for {instrument} "
660+
f"from {start_date} to {end_date}."
661+
)

imap_data_access/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""Utility classes and enums for IMAP Data Access."""
2+
3+
from enum import Enum
4+
5+
6+
class ReleaseType(Enum):
7+
"""Enum for release types."""
8+
9+
RELEASE = "release"
10+
EARLY_RELEASE = "early-release"
11+
UNRELEASE = "unrelease"

0 commit comments

Comments
 (0)