From 218a36be3aba2c4e50f19b555472f296d30914a2 Mon Sep 17 00:00:00 2001 From: Kimberly Meechan Date: Fri, 15 Mar 2024 14:08:20 +0000 Subject: [PATCH 1/2] convert general submodule docstrings to numpydoc --- brainglobe_utils/general/exceptions.py | 7 +- brainglobe_utils/general/list.py | 58 ++++-- brainglobe_utils/general/numerical.py | 64 +++++-- brainglobe_utils/general/pathlib.py | 18 +- brainglobe_utils/general/string.py | 35 +++- brainglobe_utils/general/system.py | 245 ++++++++++++++++++------- 6 files changed, 324 insertions(+), 103 deletions(-) diff --git a/brainglobe_utils/general/exceptions.py b/brainglobe_utils/general/exceptions.py index 036f0b0..9423470 100644 --- a/brainglobe_utils/general/exceptions.py +++ b/brainglobe_utils/general/exceptions.py @@ -2,9 +2,10 @@ class CommandLineInputError(Exception): """Exception raised for incorrect or illogical command line inputs that are not caught elsewhere. - Attributes: - expression -- input expression in which the error occurred - message -- explanation of the error + Attributes + ---------- + message : str + explanation of the error """ def __init__(self, message): diff --git a/brainglobe_utils/general/list.py b/brainglobe_utils/general/list.py index 60b9a6e..c17fcda 100644 --- a/brainglobe_utils/general/list.py +++ b/brainglobe_utils/general/list.py @@ -4,8 +4,16 @@ def remove_empty_string(str_list): """ Removes any empty strings from a list of strings - :param str_list: List of strings - :return: List of strings without the empty strings + + Parameters + ---------- + str_list : list of str + List of strings. + + Returns + ------- + list of str + List of strings without the empty strings. """ return list(filter(None, str_list)) @@ -17,11 +25,23 @@ def unique_elements_lists(list_in): def check_unique_list(in_list, natural_sort=True): """ - Checks if all the items in a list are unique or not - :param list in_list: Input list - :param bool natural_sort: Sort the resulting items naturally - (default: True) - :return: True/False and a list of any repeated values + Checks if all the items in a list are unique or not. + + Parameters + ---------- + in_list : list + Input list. + + natural_sort : bool, optional + Sort the resulting items naturally. Default is True. + + Returns + ------- + bool + True if all items are unique, False otherwise. + + list + A list of any repeated values. """ unique = set(in_list) repeated_items = [] @@ -43,11 +63,25 @@ def common_member(a, b, natural_sort=True): """ Checks if two lists (or sets) have a common member, and if so, returns the common members. - :param a: First list (or set) - :param b: Second list (or set) - :param bool natural_sort: Sort the resulting items naturally - (default: True) - :return: True/False and the list of values + + Parameters + ---------- + a : list or set + First list (or set). + + b : list or set + Second list (or set). + + natural_sort : bool, optional + Sort the resulting items naturally. Default is True. + + Returns + ------- + bool + True if common members exist, False otherwise. + + list + The list of common values. """ a_set = set(a) b_set = set(b) diff --git a/brainglobe_utils/general/numerical.py b/brainglobe_utils/general/numerical.py index 4d9df2f..981f30c 100644 --- a/brainglobe_utils/general/numerical.py +++ b/brainglobe_utils/general/numerical.py @@ -3,9 +3,17 @@ def is_even(num): """ - Returns True if a number is even - :param num: - :return: + Returns True if a number is even. + + Parameters + ---------- + num : int + Input number. + + Returns + ------- + bool + True if number is even, otherwise False. """ if num == 0: raise NotImplementedError( @@ -20,11 +28,26 @@ def is_even(num): def check_positive_float(value, none_allowed=True): """ - Used in argparse to enforce positive floats - FromL https://stackoverflow.com/questions/14117415 - :param value: Input value - :param none_allowed: If false, throw an error for None values - :return: Input value, if it's positive + Used in argparse to enforce positive floats. + Source: https://stackoverflow.com/questions/14117415 + + Parameters + ---------- + value : float + Input value. + + none_allowed : bool, optional + If False, throw an error for None values. + + Returns + ------- + float + Input value, if it's positive. + + Raises + ------ + argparse.ArgumentTypeError + If input value is invalid. """ ivalue = value if ivalue is not None: @@ -42,11 +65,26 @@ def check_positive_float(value, none_allowed=True): def check_positive_int(value, none_allowed=True): """ - Used in argparse to enforce positive ints - FromL https://stackoverflow.com/questions/14117415 - :param value: Input value - :param none_allowed: If false, throw an error for None values - :return: Input value, if it's positive + Used in argparse to enforce positive ints. + Source: https://stackoverflow.com/questions/14117415 + + Parameters + ---------- + value : int + Input value. + + none_allowed : bool, optional + If False, throw an error for None values. + + Returns + ------- + int + Input value, if it's positive. + + Raises + ------ + argparse.ArgumentTypeError + If input value is invalid. """ ivalue = value if ivalue is not None: diff --git a/brainglobe_utils/general/pathlib.py b/brainglobe_utils/general/pathlib.py index 57c8545..0f5845d 100644 --- a/brainglobe_utils/general/pathlib.py +++ b/brainglobe_utils/general/pathlib.py @@ -1,8 +1,18 @@ def append_to_pathlib_stem(path, string_to_append): """ - Appends a string to the stem of a pathlib object - :param path: pathlib path - :param string_to_append: string to append to the stem - :return: pathlib object with the string added to the stem + Appends a string to the stem of a pathlib object. + + Parameters + ---------- + path : pathlib.Path + Pathlib path. + + string_to_append : str + String to append to the stem. + + Returns + ------- + pathlib.Path + Pathlib object with the string added to the stem. """ return path.parent / (path.stem + string_to_append + path.suffix) diff --git a/brainglobe_utils/general/string.py b/brainglobe_utils/general/string.py index 96b35df..b7702de 100644 --- a/brainglobe_utils/general/string.py +++ b/brainglobe_utils/general/string.py @@ -12,15 +12,32 @@ def get_text_lines( encoding="utf8", ): """ - Return only the nth line of a text file - :param file: Any text file - :param return_lines: Which specific line/lines to read - :param rstrip: Remove trailing characters - :param sort: If true, naturally sort the data - :param remove_empty_lines: If True, ignore empty lines - :param encoding: What encoding the text file has. - Default: None (platform dependent) - :return: The nth line + Return only the nth line of a text file. + + Parameters + ---------- + file : str or pathlib.Path + Path to any text file. + + return_lines : int, optional + Which specific line to read. + + rstrip : bool, optional + Whether to remove trailing characters. + + sort : bool, optional + If True, naturally sort the data. + + remove_empty_lines : bool, optional + If True, ignore empty lines. + + encoding : str, optional + What encoding the text file has. + + Returns + ------- + str or list of str + The nth line or lines. """ with open(file, encoding=encoding) as f: lines = f.readlines() diff --git a/brainglobe_utils/general/system.py b/brainglobe_utils/general/system.py index 253b743..611b6c8 100644 --- a/brainglobe_utils/general/system.py +++ b/brainglobe_utils/general/system.py @@ -22,12 +22,24 @@ def replace_extension(file, new_extension, check_leading_period=True): """ - Replaces the file extension of a given file - :param str file: Input file with file extension to replace - :param str new_extension: New file extension - :param bool check_leading_period: If True, any leading period of the - new extension is removed, preventing "file..txt" - :return str: File with new file extension + Replaces the file extension of a given file. + + Parameters + ---------- + file : str + Input file path with file extension to replace. + + new_extension : str + New file extension. + + check_leading_period : bool, optional + If True, any leading period of the new extension is removed, + preventing "file..txt". + + Returns + ------- + str + File path with new file extension. """ if check_leading_period: new_extension = remove_leading_character(new_extension, ".") @@ -37,10 +49,20 @@ def replace_extension(file, new_extension, check_leading_period=True): def remove_leading_character(string, character): """ If "string" starts with "character", strip that leading character away. - Only removes the first instance - :param string: - :param character: - :return: String without the specified, leading character + Only removes the first instance. + + Parameters + ---------- + string : str + Input string. + + character : str + Character to be stripped if found at the beginning of the string. + + Returns + ------- + str + String without the specified leading character. """ if string.startswith(character): return string[1:] @@ -50,9 +72,13 @@ def remove_leading_character(string, character): def ensure_directory_exists(directory): """ - If a directory doesn't exist, make it. Works for pathlib objects, and - strings. - :param directory: + If a directory doesn't exist, make it. Works for pathlib objects, + and strings. + + Parameters + ---------- + directory : str or pathlib.Path + Directory to be created if it doesn't exist. """ if isinstance(directory, str): if not os.path.exists(directory): @@ -64,16 +90,26 @@ def ensure_directory_exists(directory): def get_sorted_file_paths(file_path, file_extension=None, encoding=None): """ Sorts file paths with numbers "naturally" (i.e. 1, 2, 10, a, b), not - lexiographically (i.e. 1, 10, 2, a, b). - :param str file_path: File containing file_paths in a text file, - or as a list. - :param str file_extension: Optional file extension (if a directory - is passed) - :param encoding: If opening a text file, what encoding it has. - Default: None (platform dependent) - :return: Sorted list of file paths - """ + lexicographically (i.e. 1, 10, 2, a, b). + + Parameters + ---------- + file_path : list of str or str + List of file paths, or path of a text file containing these paths, + or path of a directory containing files. + + file_extension : str, optional + Only return filepaths with this extension (if a directory is passed). + + encoding : str, optional + If opening a text file, what encoding it has. + Default is None (platform dependent). + Returns + ------- + list of str + Sorted list of file paths. + """ if isinstance(file_path, list): return natsorted(file_path) @@ -101,10 +137,20 @@ def get_sorted_file_paths(file_path, file_extension=None, encoding=None): def check_path_in_dir(file_path, directory_path): """ - Check if a file path is in a directory - :param file_path: Full path to a file - :param directory_path: Full path to a directory the file may be in - :return: True if the file is in the directory + Check if a file path is in a directory. + + Parameters + ---------- + file_path : str or pathlib.Path + Full path to a file. + + directory_path : str or pathlib.Path + Full path to a directory the file may be in. + + Returns + ------- + bool + True if the file is in the directory, False otherwise. """ directory = Path(directory_path) parent = Path(file_path).parent @@ -120,21 +166,39 @@ def get_num_processes( enforce_single_cpu=True, ): """ - Determine how many CPU cores to use, based on a minimum number of cpu cores - to leave free, and an optional max number of processes. + Determine how many CPU cores to use, based on a minimum number of CPU cores + to leave free, and an optional maximum number of processes. Cluster computing aware for the SLURM job scheduler, and not yet implemented for other environments. - :param int min_free_cpu_cores: How many cpu cores to leave free - :param float ram_needed_per_process: Memory requirements per process. Set - this to ensure that the number of processes isn't too high. - :param float fraction_free_ram: Fraction of the ram to ensure stays free - regardless of the current program. - :param int n_max_processes: Maximum number of processes - :param float max_ram_usage: Maximum amount of RAM (in bytes) - to use (allthough available may be lower) - :param enforce_single_cpu: Ensure that >=1 cpu cores are chosen - :return: Number of processes to + + Parameters + ---------- + min_free_cpu_cores : int, optional + How many CPU cores to leave free. + + ram_needed_per_process : float, optional + Memory requirements per process. Set this to ensure that the number of + processes isn't too high. + + fraction_free_ram : float, optional + Fraction of the RAM to ensure stays free regardless of the current + program. + + n_max_processes : int, optional + Maximum number of processes. + + max_ram_usage : float, optional + Maximum amount of RAM (in bytes) to use (although available may be + lower). + + enforce_single_cpu : bool, optional + Ensure that >=1 CPU core is chosen. + + Returns + ------- + int + Number of processes to use. """ logging.debug("Determining the maximum number of CPU cores to use") @@ -215,14 +279,25 @@ def how_many_cores_with_sufficient_ram( amount of free RAM. N.B. this does not relate to how many CPU cores are actually available. - :param float ram_needed_per_cpu: Memory requirements per process. Set - this to ensure that the number of processes isn't too high. - :param float fraction_free_ram: Fraction of the ram to ensure stays free - regardless of the current program. - :param float max_ram_usage: The Maximum amount of RAM (in bytes) - to use (although available may be lower) - :return: How many CPU cores could be theoretically used based on - the amount of free RAM + Parameters + ---------- + ram_needed_per_cpu : float + Memory requirements per process. Set this to ensure that the number of + processes isn't too high. + + fraction_free_ram : float, optional + Fraction of the RAM to ensure stays free regardless of the current + program. + + max_ram_usage : float, optional + The Maximum amount of RAM (in bytes) to use (although available may be + lower). + + Returns + ------- + int + How many CPU cores could be theoretically used based on the amount of + free RAM. """ try: @@ -252,8 +327,16 @@ def how_many_cores_with_sufficient_ram( def disk_free_gb(file_path): """ Return the free disk space, on a disk defined by a file path. - :param file_path: File path on the disk to be checked - :return: Free space in GB + + Parameters + ---------- + file_path : str + File path on the disk to be checked. + + Returns + ------- + float + Free space in GB. """ if platform.system() == "Windows": drive, _ = os.path.splitdrive(file_path) @@ -266,8 +349,12 @@ def disk_free_gb(file_path): def get_free_ram(): """ - Returns the amount of free RAM in bytes - :return: Available RAM in bytes + Returns the amount of free RAM in bytes. + + Returns + ------- + int + Available RAM in bytes. """ return psutil.virtual_memory().available @@ -280,10 +367,16 @@ def safe_execute_command(cmd, log_file_path=None, error_file_path=None): From https://github.com/SainsburyWellcomeCentre/amap_python by Charly Rousseau (https://github.com/crousseau). - :param cmd: - :param log_file_path: - :param error_file_path: - :return: + Parameters + ---------- + cmd : str + Command to be executed. + + log_file_path : str, optional + File path to log the output. + + error_file_path : str, optional + File path to log any errors. """ if log_file_path is None: log_file_path = os.path.abspath( @@ -341,8 +434,15 @@ class SafeExecuteCommandError(Exception): def delete_directory_contents(directory, progress=False): """ - Removes all contents of a directory - :param directory: Directory with files to be removed + Removes all contents of a directory. + + Parameters + ---------- + directory : str + Directory with files to be removed. + + progress : bool, optional + Whether to show a progress bar. """ files = os.listdir(directory) if progress: @@ -355,9 +455,22 @@ def delete_directory_contents(directory, progress=False): def check_path_exists(file): """ - Returns True is a file exists, otherwise throws a FileNotFoundError - :param file: Input file - :return: True, if the file exists + Returns True if a file exists, otherwise throws a FileNotFoundError. + + Parameters + ---------- + file : str or pathlib.Path + Input file. + + Returns + ------- + bool + True if the file exists. + + Raises + ------ + FileNotFoundError + If the file doesn't exist. """ file = Path(file) if file.exists(): @@ -368,9 +481,17 @@ def check_path_exists(file): def catch_input_file_error(path): """ - Catches if an input path doesn't exist, and returns an informative error - :param path: Input file path - default) + Catches if an input path doesn't exist, and returns an informative error. + + Parameters + ---------- + path : str or pathlib.Path + Input file path. + + Raises + ------ + CommandLineInputError + If the file doesn't exist. """ try: check_path_exists(path) From b3c6a18828fd6bec578447c7a70d2aa5d4372eb4 Mon Sep 17 00:00:00 2001 From: Kimberly Meechan Date: Fri, 15 Mar 2024 16:19:41 +0000 Subject: [PATCH 2/2] update docstring of is_even --- brainglobe_utils/general/numerical.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/brainglobe_utils/general/numerical.py b/brainglobe_utils/general/numerical.py index 981f30c..d45e60a 100644 --- a/brainglobe_utils/general/numerical.py +++ b/brainglobe_utils/general/numerical.py @@ -3,7 +3,7 @@ def is_even(num): """ - Returns True if a number is even. + Returns True if the non-zero input number is even. Parameters ---------- @@ -14,6 +14,11 @@ def is_even(num): ------- bool True if number is even, otherwise False. + + Raises + ------ + NotImplementedError + If the input number is zero. """ if num == 0: raise NotImplementedError(