common/py_libs/yaml_util.py (44 lines of code) (raw):
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module to help format and clean YAML dumps."""
import enum
from typing import Any, Callable, Dict, Iterable, List, Type
import yaml
class YamlStrValue(str):
"""class that is used to identify yaml str values (not keys)."""
def _visit_fields(fn: Callable,
container: Any,
parent_container: Any = None) -> Any:
"""Visit all fields in the container and set values with the provided fn.
This traverses the container using DFS. A container in this context is a
heirarchical structure that contain nested dictionaries, lists, and scalars
that can be traversed as a tree.
"""
# Expand nested dicts.
if isinstance(container, Dict):
for key, val in list(container.items()):
container[key] = _visit_fields(fn, val, container)
# Expand list elements.
elif isinstance(container, List):
for i, val in enumerate(container):
container[i] = _visit_fields(fn, val, container)
# Base case to return the fn called with a leaf value.
else:
return fn(container, parent_container)
return container
def cast_container_types(container: Any, source_type: Type,
target_type: Type) -> Any:
"""Returns container with leaf fields cast from source to target type."""
return _visit_fields(
(lambda x, _: target_type(x) if isinstance(x, source_type) else x),
container)
def get_container_enums_as_names(container: Any) -> Any:
"""Returns container with enum leaf fields cast to name string."""
return _visit_fields(
(lambda x, _: x.name if isinstance(x, enum.Enum) else x),
container)
def remove_null_fields(container: Iterable) -> None:
"""Remove null fields from the container.
This modifies the container in place and returns None.
Field values (included nested fields) that are considered null are:
* None values
* empty lists
"""
# Expand nested dicts.
if isinstance(container, Dict):
for key, val in list(container.items()):
if val is None:
del container[key]
elif val == []:
del container[key]
elif val == "":
del container[key]
else:
remove_null_fields(val)
# Expand list elements.
elif isinstance(container, List):
for _, val in enumerate(container):
remove_null_fields(val)
def quoted_presenter(dumper, data):
"""YAML Presenter that can be used to wrap a value type in double quotes.
Usage Example:
dict = yaml_util.cast_container_types(dict, str, yaml_util.YamlStrValue)
yaml.add_representer(yaml_util.YamlStrValue, yaml_util.quoted_presenter)
"""
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="\"")
class IndentedDumper(yaml.Dumper):
"""This dumper can be used to have list dashes after the indent.
Default formatting:
parent:
- child
Formatting with IndentedDumper:
parent:
- child
"""
def increase_indent(self, flow=False, indentless=False):
return super().increase_indent(flow, False)