From 15eb9374534c1a01312d4f67668710f81c260c5a Mon Sep 17 00:00:00 2001 From: Maurizio Porrato Date: Sat, 12 Aug 2023 18:15:36 +0100 Subject: [PATCH] Add some missing docstrings --- src/operator_repo/utils.py | 91 ++++++++++++++++++++++++++++++++++++-- tests/__init__.py | 87 ++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/src/operator_repo/utils.py b/src/operator_repo/utils.py index 3985160..8db89ee 100644 --- a/src/operator_repo/utils.py +++ b/src/operator_repo/utils.py @@ -16,8 +16,28 @@ log = logging.getLogger(__name__) def _find_yaml(path: Path) -> Path: - """Look for yaml files with alternate extensions""" + """ + Find a YAML file by looking for files with alternate extensions. + This function searches for a YAML file by trying multiple extensions (.yaml and .yml) + and checking if the file exists. It is used to locate YAML files with different possible + extensions in order to provide flexibility when specifying file paths. + + Args: + path (Path): The path to the file with or without a YAML extension. + + Returns: + Path: The path to the found YAML file. + + Raises: + FileNotFoundError: If a YAML file with any of the tried extensions cannot be found. + + Example: + file_path = Path("my_file.json") + yaml_path = _find_yaml(file_path) + # If "my_file.yaml" or "my_file.yml" exists, yaml_path will point to the found YAML file. + # Otherwise, a FileNotFoundError will be raised. + """ if path.is_file(): return path tries = [path] @@ -33,8 +53,28 @@ def _find_yaml(path: Path) -> Path: def _load_yaml_strict(path: Path) -> Any: - """Returns the parsed contents of the YAML file at the given path""" + """ + Load and parse the contents of a YAML file at the given path. + This function reads the contents of the specified YAML file and attempts to parse it + using the `yaml.safe_load` method. If the YAML document contains multiple documents or + if it's not a valid YAML document, exceptions are raised accordingly. + + Args: + path (Path): The path to the YAML file to be loaded. + + Returns: + Any: The parsed contents of the YAML file. + + Raises: + OperatorRepoException: If the YAML file contains multiple documents. + OperatorRepoException: If the YAML file is not a valid YAML document. + + Example: + yaml_path = Path("my_file.yaml") + yaml_content = _load_yaml_strict(yaml_path) + # The parsed contents of the YAML file will be stored in the `yaml_content` variable. + """ log.debug("Loading %s", path) with path.open("r") as yaml_file: try: @@ -48,13 +88,58 @@ def _load_yaml_strict(path: Path) -> Any: def load_yaml(path: Path) -> Any: - """Same as _load_yaml_strict but tries both .yaml and .yml extensions""" + """ + Load and parse the contents of a YAML file at the given path with alternate extensions. + + Args: + path (Path): The path to the file with or without a YAML extension. + + Returns: + Any: The parsed contents of the YAML file. + + Raises: + OperatorRepoException: If the YAML file contains multiple documents. + OperatorRepoException: If the YAML file is not a valid YAML document. + + Example: + file_path = Path("my_file.json") + yaml_content = load_yaml(file_path) + # If "my_file.yaml" or "my_file.yml" exists, the parsed contents of the YAML file + # will be stored in the `yaml_content` variable. + """ return _load_yaml_strict(_find_yaml(path)) def lookup_dict( data: dict[str, Any], path: str, default: Any = None, separator: str = "." ) -> Any: + """ + Retrieve a value from a nested dictionary using a specific path of keys. + + This function allows you to access a value in a nested dictionary by providing a + dot-separated path of keys. If the path exists in the dictionary, the corresponding + value is returned. If the path does not exist, the specified default value is returned. + + Args: + data (dict): The nested dictionary from which to retrieve the value. + path (str): A dot-separated string representing the path to the desired value. + default (Any, optional): The value to return if the path does not exist. Defaults to None. + separator (str, optional): The separator used to split the path into keys. Defaults to ".". + + Returns: + Any: The value at the specified path if found, otherwise the default value. + + Example: + data = { + "a": { + "b": { + "c": 42 + } + } + } + value = lookup_dict(data, "a.b.c") + # value will be 42 + """ keys = path.split(separator) subtree = data for key in keys: diff --git a/tests/__init__.py b/tests/__init__.py index ea85694..dce0965 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,6 +4,28 @@ import yaml def merge(a, b, path=None): + """ + Recursively merge two dictionaries, with values from the second dictionary (b) + overwriting corresponding values in the first dictionary (a). This function can + handle nested dictionaries. + + Args: + a (dict): The base dictionary to merge into. + b (dict): The dictionary with values to merge into the base dictionary. + path (list of str, optional): A list representing the current path in the recursive merge. + This argument is used internally for handling nested dictionaries. Users typically + don't need to provide this argument. Defaults to None. + + Returns: + dict: The merged dictionary (a) after incorporating values from the second dictionary (b). + + Example: + dict_a = {"key1": 42, "key2": {"subkey1": "value1"}} + dict_b = {"key2": {"subkey2": "value2"}, "key3": "value3"} + merged_dict = merge(dict_a, dict_b) + # merged_dict will be: + # {"key1": 42, "key2": {"subkey1": "value1", "subkey2": "value2"}, "key3": "value3"} + """ if path is None: path = [] for key in b: @@ -18,6 +40,37 @@ def merge(a, b, path=None): def create_files(path, *contents): + """ + Create files and directories at the specified path based on the provided content. + + This function allows you to create files and directories at a specified path. + The function accepts a variable number of content dictionaries, where each + dictionary represents a file or directory to be created. The keys in the dictionary + represent the filenames or directory names, and the values represent the content + of the files or subdirectories. + + Args: + path (str): The path where the files and directories should be created. + *contents (dict): Variable number of dictionaries representing files and directories. + For files, the dictionary should have a single key-value pair where the key is + the filename and the value is the content of the file. For directories, the + dictionary should have a single key with a value of None. + + Returns: + None + + Example: + create_files( + "/my_folder", + {"file1.txt": "Hello, World!"}, + {"subfolder": None}, + {"config.yaml": {"key": "value"}}, + ) + + In this example, the function will create a file "file1.txt" with content "Hello, World!" + in the "/my_folder" directory, create an empty subdirectory "subfolder", and create a + file "config.yaml" with the specified YAML content. + """ root = Path(path) for element in contents: for file_name, content in element.items(): @@ -35,6 +88,40 @@ def create_files(path, *contents): def bundle_files( operator_name, bundle_version, annotations=None, csv=None, other_files=None ): + """ + Create a bundle of files and metadata for an Operator package. + + This function generates a bundle of files and metadata for an Operator package, + including annotations, a CSV (ClusterServiceVersion) file, and other additional files. + + Args: + operator_name (str): The name of the Operator. + bundle_version (str): The version of the Operator bundle. + annotations (dict, optional): Additional annotations for the bundle annotations.yaml file. + Defaults to None. + csv (dict, optional): Additional content to merge with the base CSV (ClusterServiceVersion) + file. Defaults to None. + other_files (dict, optional): Additional files to be included in the bundle. + Defaults to None. + + Returns: + dict: A dictionary representing the bundle, including annotations.yaml and CSV files, + and other additional files. + + Example: + bundle = bundle_files( + operator_name="my-operator", + bundle_version="1.0.0", + annotations={"custom.annotation": "value"}, + csv={"spec": {"installModes": ["AllNamespaces"]}}, + other_files={"README.md": "# My Operator Documentation"} + ) + + In this example, the function will create a dictionary representing a bundle for the Operator + "my-operator" with version "1.0.0". The annotations.yaml file will contain the provided custom + annotation, the CSV file will have additional installation modes, and the README.md file will + be included as an additional file in the bundle. + """ bundle_path = f"operators/{operator_name}/{bundle_version}" base_annotations = { "operators.operatorframework.io.bundle.mediatype.v1": "registry+v1",