Source code for xcube.core.edit

# The MIT License (MIT)
# Copyright (c) 2019 by the xcube development team and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os.path
import shutil
import warnings
from typing import Callable, Type

import xarray as xr
import zarr

from xcube.core.optimize import optimize_dataset
from xcube.core.update import update_dataset_attrs
from xcube.util.config import load_configs

_NO_MANUAL_EDIT = ['geospatial_lon_min', 'geospatial_lon_max', 'geospatial_lon_units', 'geospatial_lon_resolution',
                   'geospatial_lat_min', 'geospatial_lat_max', 'geospatial_lat_units', 'geospatial_lat_resolution',
                   'time_coverage_start', 'time_coverage_end']


[docs]def edit_metadata(input_path: str, output_path: str = None, metadata_path: str = None, update_coords: bool = False, in_place: bool = False, monitor: Callable[..., None] = None, exception_type: Type[Exception] = ValueError): """ Edit the metadata of an xcube dataset. Editing the metadata because it may be incorrect, inconsistent or incomplete. The metadata attributes should be given by a yaml file with the keywords to be edited. The function currently works only for data cubes using ZARR format. :param input_path: Path to input dataset with ZARR format. :param output_path: Path to output dataset with ZARR format. May contain "{input}" template string, which is replaced by the input path's file name without file name extentsion. :param metadata_path: Path to the metadata file, which will edit the existing metadata. :param update_coords: Whether to update the metadata about the coordinates. :param in_place: Whether to modify the dataset in place. If False, a copy is made and *output_path* must be given. :param monitor: A progress monitor. :param exception_type: Type of exception to be used on value errors. """ input_path = os.path.abspath(os.path.normpath(input_path)) if not os.path.isfile(os.path.join(input_path, '.zgroup')): raise exception_type('Input path must point to ZARR dataset directory.') if in_place: output_path = input_path else: if not output_path: raise exception_type(f'Output path must be given.') if '{input}' in output_path: base_name, _ = os.path.splitext(os.path.basename(input_path)) output_path = output_path.format(input=base_name) output_path = os.path.abspath(os.path.normpath(output_path)) if os.path.exists(output_path): raise exception_type(f'Output path already exists.') if not in_place: shutil.copytree(input_path, output_path) if monitor is None: # noinspection PyUnusedLocal def monitor(*args): pass cube = zarr.open(output_path) if update_coords: with xr.open_zarr(output_path) as ds: ds_attrs = update_dataset_attrs(ds, update_existing=False, in_place=True).attrs for key in ds_attrs: cube.attrs.update({key: ds_attrs[key]}) if metadata_path: new_metadata = load_configs(metadata_path) for element in new_metadata: if 'output_metadata' in element: _edit_keyvalue_in_metadata(cube, new_metadata, element, monitor) else: if cube.__contains__(element): _edit_keyvalue_in_metadata(cube[element], new_metadata, element, monitor) else: warnings.warn(f'The variable "{element}" could not be found in the xcube dataset. ' f'Please check spelling of it.') # the metadata attrs of a consolidated xcube dataset may not be changed # (https://zarr.readthedocs.io/en/stable/api/convenience.html#zarr.convenience.consolidate_metadata) # therefore after changing metadata the xcube dataset needs to be consolidated once more. if os.path.exists(os.path.join(output_path, '.zmetadata')): optimize_dataset(output_path, in_place=True)
def _edit_keyvalue_in_metadata(cube, new_metadata, element, monitor): for key in new_metadata[element].keys(): if key in _NO_MANUAL_EDIT: monitor(f'"{key}" is not updated in the global attributes of the xcube dataset. ' f'Please use "--coords" for updating coordinate information.') else: cube.attrs.update({key: new_metadata[element][key]}) if 'output_metadata' in element: monitor(f'Updated "{key}" in the global attributes of the xcube dataset.') else: monitor(f'Updated "{key}" in the attributes of "{element}" in the xcube dataset.')