Source code for pudl.scripts.pudl_datastore

"""CLI for managing raw data inputs to the PUDL data processing pipeline."""

from __future__ import annotations

import pathlib
import sys
from typing import TYPE_CHECKING

import click

from pudl.workspace.datastore import ZenodoDoiSettings

if TYPE_CHECKING:
    from pudl.workspace.datastore import Datastore

[docs] _KNOWN_DATASETS = sorted(ZenodoDoiSettings.model_fields)
[docs] def _print_partitions(dstore: Datastore, datasets: list[str]) -> None: """Print known partition keys and values for each of the datasets.""" from pudl.workspace.datastore import ZenodoFetcher # noqa: PLC0415 for single_ds in datasets: partitions = dstore.get_datapackage_descriptor(single_ds).get_partitions() print(f"\nPartitions for {single_ds} ({ZenodoFetcher().get_doi(single_ds)}):") for partition_key in sorted(partitions): # try-except required because ferc2 has parts with heterogenous types # that therefore can't be sorted: [1, 2, None] try: parts = sorted(partitions[partition_key]) except TypeError: parts = partitions[partition_key] print(f" {partition_key}: {', '.join(str(x) for x in parts)}") if not partitions: print(" -- no known partitions --")
[docs] def _parse_key_values( ctx: click.core.Context, param: click.Option, values: str, ) -> dict[str, str]: """Parse key-value pairs into a Python dictionary. Transforms a command line argument of the form: k1=v1,k2=v2,k3=v3... into: {k1:v1, k2:v2, k3:v3, ...} """ out_dict = {} for val in values: for key_value in val.split(","): key, value = key_value.split("=") out_dict[key] = value return out_dict
@click.command( context_settings={"help_option_names": ["-h", "--help"]}, ) @click.argument( "datasets", nargs=-1, type=click.Choice(_KNOWN_DATASETS), ) @click.option( "--all", "all_datasets", is_flag=True, default=False, help=( "Operate on all known datasets. Mutually exclusive with specifying individual " "DATASETS arguments. Useful for automation where maintaining an explicit list " "of dataset names would risk getting out of sync with ZenodoDoiSettings." ), ) @click.option( "--validate", is_flag=True, default=False, help="Validate the contents of locally cached data, but don't download anything.", ) @click.option( "--list-partitions", help="List the available partition keys and values for each specified dataset.", is_flag=True, default=False, ) @click.option( "--partition", "-p", multiple=True, help=( "Only operate on dataset partitions matching these conditions. The argument " "should have the form: key1=val1,key2=val2,... Conditions are combined with " "a boolean AND, functionally meaning each key can only appear once. " "If a key is repeated, only the last value is used. " "So state=ca,year=2022 will retrieve all California data for 2022, and " "state=ca,year=2021,year=2022 will also retrieve California data for 2022, " "while state=ca by itself will retrieve all years of California data." ), callback=_parse_key_values, ) @click.option( "--bypass-local-cache", is_flag=True, default=False, help=( "If enabled, locally cached data will not be used. Instead, a new copy will be " "downloaded from Zenodo or the cloud cache if specified." ), ) @click.option( "--cloud-cache-path", type=str, default="s3://pudl.catalyst.coop/zenodo", help=( "Load cached inputs from cloud object storage (S3 or GCS) . This is typically " "much faster and more reliable than downloading from Zenodo directly. By " "default we read from the cache in PUDL's free, public AWS Open Data Registry " "bucket." ), ) @click.option( "--logfile", help="If specified, write logs to this file.", type=click.Path( exists=False, resolve_path=True, path_type=pathlib.Path, ), ) @click.option( "--loglevel", default="INFO", type=click.Choice( ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], case_sensitive=False ), )
[docs] def main( datasets: tuple[str, ...], all_datasets: bool, validate: bool, list_partitions: bool, partition: dict[str, int | str], cloud_cache_path: str, bypass_local_cache: bool, logfile: pathlib.Path, loglevel: str, ) -> int: """Manage the raw data inputs to the PUDL data processing pipeline. Download the raw FERC Form 2 data: pudl_datastore ferc2 Download the raw FERC Form 2 data only for 2021: pudl_datastore ferc2 --partition year=2021 Re-download the raw FERC Form 2 data for 2021 even if you already have it: pudl_datastore ferc2 --partition year=2021 --bypass-local-cache Validate all California EPA CEMS data in the local datastore: pudl_datastore epacems --validate --partition state=ca List the available partitions in the EIA-860 and EIA-923 datasets: pudl_datastore eia860 eia923 --list-partitions Download all known datasets (e.g. in automation): pudl_datastore --all """ # Deferred to keep --help fast; see pudl/scripts/__init__.py for rationale. import pudl # noqa: PLC0415 from pudl.workspace.datastore import ( # noqa: PLC0415 Datastore, fetch_resources, validate_cache, ) from pudl.workspace.setup import PudlPaths # noqa: PLC0415 logger = pudl.logging_helpers.get_logger(__name__) pudl.logging_helpers.configure_root_logger( logfile=str(logfile) if logfile else None, loglevel=loglevel ) if all_datasets and datasets: raise click.UsageError("Cannot combine --all with explicit DATASETS arguments.") if not all_datasets and not datasets: logger.warning("No datasets specified, nothing to do.") return 0 dataset_list = _KNOWN_DATASETS if all_datasets else list(datasets) cache_path = None if not bypass_local_cache: cache_path = PudlPaths().pudl_input dstore = Datastore( cloud_cache_path=cloud_cache_path, local_cache_path=cache_path, ) if partition: logger.info(f"Only considering resource partitions: {partition}") if list_partitions: _print_partitions(dstore, dataset_list) elif validate: validate_cache(dstore, dataset_list, partition) else: fetch_resources( dstore=dstore, datasets=dataset_list, partition=partition, cloud_cache_path=cloud_cache_path, bypass_local_cache=bypass_local_cache, ) return 0
if __name__ == "__main__": sys.exit(main())