From 42b73de4b6029f7a9a8e41393ae0af169945a8ad Mon Sep 17 00:00:00 2001 From: Evan Harvey Date: Tue, 12 Nov 2024 13:05:05 -0700 Subject: [PATCH] opencsp/common/lib/tool: test docs --- opencsp/common/lib/tool/dict_tools.py | 258 +++++++++++++++++++-- opencsp/common/lib/tool/file_tools.py | 31 +++ opencsp/common/lib/tool/list_tools.py | 42 ++++ opencsp/common/lib/tool/math_tools.py | 67 ++++++ opencsp/common/lib/tool/system_tools.py | 1 + opencsp/common/lib/tool/time_date_tools.py | 203 +++++++++++++++- opencsp/common/lib/tool/tk_tools.py | 89 ++++++- opencsp/common/lib/tool/unit_conversion.py | 84 +++++++ opencsp/test/test_DocStringsExist.py | 19 ++ 9 files changed, 758 insertions(+), 36 deletions(-) diff --git a/opencsp/common/lib/tool/dict_tools.py b/opencsp/common/lib/tool/dict_tools.py index 144866c6a..34826ba05 100644 --- a/opencsp/common/lib/tool/dict_tools.py +++ b/opencsp/common/lib/tool/dict_tools.py @@ -1,10 +1,3 @@ -""" -Utilities for dictionaries. - - - -""" - import opencsp.common.lib.tool.file_tools as ft @@ -12,20 +5,63 @@ def number_of_keys(input_dict): + """ + Return the number of keys in the given dictionary. + + Parameters + ---------- + input_dict : dict + The dictionary for which to count the keys. + + Returns + ------- + int + The number of keys in the input dictionary. + """ + # "ChatGPT 4o" assisted with generating this docstring. return len(input_dict.keys()) def sorted_keys(input_dict): """ - Return the dictionary keys, in a sorted list. - Because this constructs a list and sorts it, this is slower than using keys(), but provides the keys in a predictable order. + Return the keys of the dictionary in a sorted list. + + Parameters + ---------- + input_dict : dict + The dictionary whose keys are to be sorted. + + Returns + ------- + list + A sorted list of the keys in the input dictionary. + + Notes + ----- + This function constructs a list of keys and sorts it, which may be slower than using `keys()`, + but provides the keys in a predictable order. """ + # "ChatGPT 4o" assisted with generating this docstring. key_list = list(input_dict.keys()) key_list.sort() return key_list def list_of_values_in_sorted_key_order(input_dict): + """ + Return a list of values from the dictionary in the order of sorted keys. + + Parameters + ---------- + input_dict : dict + The dictionary from which to extract values. + + Returns + ------- + list + A list of values corresponding to the sorted keys of the input dictionary. + """ + # "ChatGPT 4o" assisted with generating this docstring. output_list = [] for key in sorted_keys(input_dict): output_list.append(input_dict[key]) @@ -42,8 +78,24 @@ def print_dict( indent=None, ): # Number of blanks to print at the beginning of each line. """ - Prints a simple dictionary, limiting print-out length both laterally and vertically. + Print a simple dictionary, limiting the output length both laterally and vertically. + + Parameters + ---------- + input_dict : dict + The dictionary to print. + max_keys : int, optional + The maximum number of keys to print. An ellipsis will be shown if there are more keys. + max_value_length : int, optional + The maximum length of values to print. An ellipsis will be shown if values exceed this length. + indent : int, optional + The number of spaces to print at the beginning of each line for indentation. + + Returns + ------- + None """ + # "ChatGPT 4o" assisted with generating this docstring. # Sort key list for consistent output order. key_list = sorted_keys(input_dict) # Content. @@ -75,8 +127,28 @@ def print_dict_of_dicts( indent_2=4, ): # Number of additional blanks to print for each second-level line. """ - Prints a one-level nested dictionary, limiting print-out length both laterally and vertically. + Print a one-level nested dictionary, limiting the output length both laterally and vertically. + + Parameters + ---------- + input_dict : dict + The dictionary to print, where values are themselves dictionaries. + max_keys_1 : int, optional + The maximum number of top-level keys to print. An ellipsis will be shown if there are more keys. + max_keys_2 : int, optional + The maximum number of second-level keys to print. An ellipsis will be shown if there are more keys. + max_value_2_length : int, optional + The maximum length of second-level values to print. An ellipsis will be shown if values exceed this length. + indent_1 : int, optional + The number of spaces to print at the beginning of each top-level line for indentation. + indent_2 : int, optional + The number of additional spaces to print for each second-level line. + + Returns + ------- + None """ + # "ChatGPT 4o" assisted with generating this docstring. # Indentation. indent_level_1 = indent_1 indent_level_2 = indent_1 + indent_2 @@ -121,8 +193,32 @@ def print_dict_of_dict_of_dicts( indent_3=4, ): # Number of additional blanks to print for each third-level line. """ - Prints a one-level nested dictionary, limiting print-out length both laterally and vertically. + Print a two-level nested dictionary, limiting the output length both laterally and vertically. + + Parameters + ---------- + input_dict : dict + The dictionary to print, where values are dictionaries that themselves contain dictionaries. + max_keys_1 : int, optional + The maximum number of top-level keys to print. An ellipsis will be shown if there are more keys. + max_keys_2 : int, optional + The maximum number of second-level keys to print. An ellipsis will be shown if there are more keys. + max_keys_3 : int, optional + The maximum number of third-level keys to print. An ellipsis will be shown if there are more keys. + max_value_3_length : int, optional + The maximum length of third-level values to print. An ellipsis will be shown if values exceed this length. + indent_1 : int, optional + The number of spaces to print at the beginning of each top-level line for indentation. + indent_2 : int, optional + The number of additional spaces to print for each second-level line. + indent_3 : int, optional + The number of additional spaces to print for each third-level line. + + Returns + ------- + None """ + # "ChatGPT 4o" assisted with generating this docstring. # Indentation. indent_level_1 = indent_1 indent_level_2 = indent_1 + indent_2 @@ -181,10 +277,29 @@ def save_list_of_one_level_dicts( list_of_one_level_dicts, output_dir, output_body, explain, error_if_dir_not_exist, first_key=None ): """ - A "one_level_dict" is a dictionary with entries that can be converted to strings and written as single entries in a csv file. - - Given a list of one_level_dicts, this routine writes the data to a csv file, one dict per line. + Save a list of one-level dictionaries to a CSV file, one dictionary per line. + + Parameters + ---------- + list_of_one_level_dicts : list of dict + A list of dictionaries to save to the CSV file. + output_dir : str + The directory where the CSV file will be saved. + output_body : str + The body of the output filename (without extension). + explain : str + An explanatory string to include in notification output. + error_if_dir_not_exist : bool + If True, raise an error if the output directory does not exist; if False, create the directory if necessary. + first_key : str, optional + A key to prioritize in the CSV heading line. + + Returns + ------- + str + The full path to the saved CSV file. """ + # "ChatGPT 4o" assisted with generating this docstring. if len(list_of_one_level_dicts) == 0: explain += ' (EMPTY)' key_list, heading_line = one_level_dict_csv_heading_line(list_of_one_level_dicts, first_key) @@ -204,6 +319,22 @@ def save_list_of_one_level_dicts( def one_level_dict_csv_heading_line(list_of_one_level_dicts, first_key): + """ + Generate the heading line for a CSV file from a list of one-level dictionaries. + + Parameters + ---------- + list_of_one_level_dicts : list of dict + A list of one-level dictionaries to extract headings from. + first_key : str, optional + A key to prioritize in the heading line. + + Returns + ------- + tuple + A tuple containing the list of keys and the heading line as a string. + """ + # "ChatGPT 4o" assisted with generating this docstring. # Catch empty input case. if len(list_of_one_level_dicts) == 0: return 'No data.' @@ -214,6 +345,22 @@ def one_level_dict_csv_heading_line(list_of_one_level_dicts, first_key): def one_level_dict_csv_heading_line_aux(first_one_level_dict, first_key): + """ + Auxiliary function to generate the heading line for a CSV file from a one-level dictionary. + + Parameters + ---------- + first_one_level_dict : dict + The first one-level dictionary to extract headings from. + first_key : str, optional + A key to prioritize in the heading line. + + Returns + ------- + tuple + A tuple containing the list of keys and the heading line as a string. + """ + # "ChatGPT 4o" assisted with generating this docstring. key_list = [] first_key_found = False for key in first_one_level_dict.keys(): @@ -240,6 +387,22 @@ def one_level_dict_csv_heading_line_aux(first_one_level_dict, first_key): def one_level_dict_csv_data_line(key_list, one_level_dict): + """ + Generate a CSV data line from a one-level dictionary. + + Parameters + ---------- + key_list : list + A list of keys to extract values from the dictionary. + one_level_dict : dict + The one-level dictionary to convert to a CSV data line. + + Returns + ------- + str + A string representing the CSV data line. + """ + # "ChatGPT 4o" assisted with generating this docstring. # Assemble list of deta entry strings. data_str_list = [] for key in key_list: @@ -262,12 +425,31 @@ def save_list_of_one_level_dict_pairs( first_key_2=None, ): """ - A "one_level_dict_pair" is a list [one_level_dict_1, one_level_dict_2] of one_level_dicts. - See above for definition of a one_level_dict. - The keys of the two dicts do not need to be the same. - - Given a list of one_level_dicts, this routine writes the data to a csv file, one dict per line. + Save a list of one-level dictionary pairs to a CSV file, one dict per line. + + Parameters + ---------- + list_of_one_level_dict_pairs : list of list + A list of pairs of one-level dictionaries to save to the CSV file. + output_dir : str + The directory where the CSV file will be saved. + output_body : str + The body of the output filename (without extension). + explain : str + An explanatory string to include in notification output. + error_if_dir_not_exist : bool + If True, raise an error if the output directory does not exist; if False, create the directory if necessary. + first_key_1 : str, optional + A key to prioritize in the heading line for the first dictionary. + first_key_2 : str, optional + A key to prioritize in the heading line for the second dictionary. + + Returns + ------- + str + The full path to the saved CSV file. """ + # "ChatGPT 4o" assisted with generating this docstring. if len(list_of_one_level_dict_pairs) == 0: explain += ' (EMPTY)' key_list_1, key_list_2, heading_line = one_level_dict_pair_csv_heading_line( @@ -290,10 +472,28 @@ def save_list_of_one_level_dict_pairs( def one_level_dict_pair_csv_heading_line(list_of_one_level_dict_pairs, first_key_1, first_key_2): """ + Generate the heading line for a CSV file from a list of one-level dictionary pairs. + This routine extracts the headings from each of the two dicts, adding suffixes "_1" or "_2" to the first and second dictionary keys, respectively. These suffixes are added regardless of whether the dictionaries have the same or different keys. + + Parameters + ---------- + list_of_one_level_dict_pairs : list of list + A list of pairs of one-level dictionaries to extract headings from. + first_key_1 : str, optional + A key to prioritize in the heading line for the first dictionary. + first_key_2 : str, optional + A key to prioritize in the heading line for the second dictionary. + + Returns + ------- + tuple + A tuple containing the list of keys for the first dictionary, the list of keys for the second dictionary, + and the combined heading line as a string. """ + # "ChatGPT 4o" assisted with generating this docstring. # Catch empty input case. if len(list_of_one_level_dict_pairs) == 0: return 'No data.' @@ -318,6 +518,24 @@ def one_level_dict_pair_csv_heading_line(list_of_one_level_dict_pairs, first_key def one_level_dict_pair_csv_data_line(key_list_1, key_list_2, one_level_dict_pair): + """ + Generate a CSV data line from a pair of one-level dictionaries. + + Parameters + ---------- + key_list_1 : list + A list of keys to extract values from the first dictionary. + key_list_2 : list + A list of keys to extract values from the second dictionary. + one_level_dict_pair : list + A pair of one-level dictionaries to convert to a CSV data line. + + Returns + ------- + str + A string representing the CSV data line for the pair of dictionaries. + """ + # "ChatGPT 4o" assisted with generating this docstring. # First dictionary data line. one_level_dict_1 = one_level_dict_pair[0] data_line_1 = one_level_dict_csv_data_line(key_list_1, one_level_dict_1) diff --git a/opencsp/common/lib/tool/file_tools.py b/opencsp/common/lib/tool/file_tools.py index 639d4fb34..d7723c24f 100755 --- a/opencsp/common/lib/tool/file_tools.py +++ b/opencsp/common/lib/tool/file_tools.py @@ -930,6 +930,37 @@ def merge_files(in_files: list[str], out_file: str, overwrite=False, remove_in_f def convert_shortcuts_to_symlinks(dirname: str): + """ + Convert Windows shortcut files (.lnk) in a specified directory to symbolic links. + + This function scans the given directory for shortcut files and replaces them with + symbolic links pointing to the original target files or directories. It handles + nested shortcuts by recursively resolving the target paths. + + Parameters + ---------- + dirname : str + The path to the directory containing the shortcut files to be converted. + + Raises + ------ + FileExistsError + If a symbolic link with the same name already exists as a file or directory. + Exception + If there is an error reading a shortcut or creating a symbolic link. + + Notes + ----- + This function is designed to work on Windows systems only. It uses the + `win32com.client` module to interact with Windows shortcuts. If the function + is executed on a non-Windows system, it will log a debug message and exit without + performing any actions. + + Examples + -------- + >>> convert_shortcuts_to_symlinks("C:\\path\\to\\shortcuts") + """ + # "ChatGPT 4o" assisted with generating this docstring. if os.name == "nt": # update dirname to use windows "\" seperators dirname = os.path.normpath(dirname) diff --git a/opencsp/common/lib/tool/list_tools.py b/opencsp/common/lib/tool/list_tools.py index b0c691435..c51ef2019 100644 --- a/opencsp/common/lib/tool/list_tools.py +++ b/opencsp/common/lib/tool/list_tools.py @@ -16,6 +16,27 @@ def remove_duplicates(list): + """ + Remove duplicate elements from a list while preserving the original order. + + Parameters + ---------- + list : list + The input list from which duplicates will be removed. + + Returns + ------- + list + A new list containing the unique elements from the input list, in the order they first appeared. + + Examples + -------- + >>> remove_duplicates([1, 2, 2, 3, 4, 4, 5]) + [1, 2, 3, 4, 5] + >>> remove_duplicates(['a', 'b', 'a', 'c']) + ['a', 'b', 'c'] + """ + # "ChatGPT 4o" assisted with generating this docstring. result = [] for x in list: if x not in result: @@ -24,6 +45,27 @@ def remove_duplicates(list): def contains_duplicates(list): + """ + Check if a list contains any duplicate elements. + + Parameters + ---------- + list : list + The input list to check for duplicates. + + Returns + ------- + bool + True if the list contains duplicates, False otherwise. + + Examples + -------- + >>> contains_duplicates([1, 2, 3, 4]) + False + >>> contains_duplicates([1, 2, 2, 3]) + True + """ + # "ChatGPT 4o" assisted with generating this docstring. return len(list) != len(remove_duplicates(list)) diff --git a/opencsp/common/lib/tool/math_tools.py b/opencsp/common/lib/tool/math_tools.py index 260ad0043..d8f3605fa 100644 --- a/opencsp/common/lib/tool/math_tools.py +++ b/opencsp/common/lib/tool/math_tools.py @@ -183,6 +183,42 @@ def none_if_nan(x): def ncr(n, r): + """ + Calculate the binomial coefficient, also known as "n choose r". + + The binomial coefficient is defined as: + \[ + C(n, r) = \frac{n!}{r!(n-r)!} + \] + where \( n \) is the total number of items, and \( r \) is the number of items to choose. + + Parameters + ---------- + n : int + The total number of items. Must be a non-negative integer. + r : int + The number of items to choose. Must be a non-negative integer less than or equal to \( n \). + + Returns + ------- + float + The binomial coefficient \( C(n, r) \). + + Raises + ------ + ValueError + If \( n \) or \( r \) are negative, or if \( r \) is greater than \( n \). + + Examples + -------- + >>> ncr(5, 2) + 10.0 + >>> ncr(10, 3) + 120.0 + >>> ncr(0, 0) + 1.0 + """ + # "ChatGPT 4o" assisted with generating this docstring. r = min(r, n - r) numer = reduce(op.mul, range(n, n - r, -1), 1) denom = reduce(op.mul, range(1, r + 1), 1) @@ -322,6 +358,37 @@ def rolling_average(data: list[float] | npt.NDArray[np.float_], window_size: int # @strict_types def lambda_symmetric_paraboloid(focal_length: numbers.Number) -> Callable[[float, float], float]: + """ + Create a lambda function representing a symmetric paraboloid. + + The symmetric paraboloid is defined by the equation: + \( z = \frac{1}{4f} (x^2 + y^2) \) + where \( f \) is the focal length of the paraboloid. + + Parameters + ---------- + focal_length : numbers.Number + The focal length of the paraboloid. Must be a positive number. + + Returns + ------- + Callable[[float, float], float] + A lambda function that takes two float arguments (x, y) and returns the corresponding z value + of the symmetric paraboloid. + + Raises + ------ + ValueError + If the focal_length is not positive. + + Examples + -------- + >>> paraboloid = lambda_symmetric_paraboloid(2.0) + >>> z_value = paraboloid(1.0, 1.0) + >>> print(z_value) + 0.125 + """ + # "ChatGPT 4o" assisted with generating this docstring. a = 1.0 / (4 * focal_length) return lambda x, y: a * (x**2 + y**2) # return FunctionXYContinuous(f"{a} * (x**2 + y**2)") diff --git a/opencsp/common/lib/tool/system_tools.py b/opencsp/common/lib/tool/system_tools.py index de6671f45..220bbb511 100644 --- a/opencsp/common/lib/tool/system_tools.py +++ b/opencsp/common/lib/tool/system_tools.py @@ -40,6 +40,7 @@ def is_production_run(): def set_is_production_run(is_production_run: bool): + "Setter for __is_production_run global" __is_production_run = is_production_run diff --git a/opencsp/common/lib/tool/time_date_tools.py b/opencsp/common/lib/tool/time_date_tools.py index c48ea9090..9a0072463 100644 --- a/opencsp/common/lib/tool/time_date_tools.py +++ b/opencsp/common/lib/tool/time_date_tools.py @@ -17,60 +17,167 @@ def print_current_date_time() -> None: + """ + Print the current date and time to the log. + + Returns + ------- + None + """ + # "ChatGPT 4o" assisted with generating this docstring. lt.info("Current Date and Time =" + current_date_time_string()) def print_current_date() -> None: + """ + Print the current date to the log. + + Returns + ------- + None + """ + # "ChatGPT 4o" assisted with generating this docstring. lt.info("Current Date =" + current_date_string()) def print_current_time() -> None: + """ + Print the current time to the log. + + Returns + ------- + None + """ + # "ChatGPT 4o" assisted with generating this docstring. lt.info("Current Time =" + current_time_string()) def current_date_time_string() -> str: - """%Y-%m-%d %H:%M:%S""" + """ + Get the current date and time as a formatted string. + + The format of the string is "%Y-%m-%d %H:%M:%S". + + Returns + ------- + str + The current date and time as a string. + """ + # "ChatGPT 4o" assisted with generating this docstring. return current_date_string() + ' ' + current_time_string() def current_date_string() -> str: - """%Y-%m-%d""" + """ + Get the current date as a formatted string. + + The format of the string is "%Y-%m-%d". + + Returns + ------- + str + The current date as a string. + """ + # "ChatGPT 4o" assisted with generating this docstring. now = datetime.now() current_date_str = now.strftime('%Y-%m-%d') return current_date_str def current_time_string() -> str: - """%H:%M:%S""" + """ + Get the current time as a formatted string. + + The format of the string is "%H:%M:%S". + + Returns + ------- + str + The current time as a string. + """ + # "ChatGPT 4o" assisted with generating this docstring. now = datetime.now() current_time_str = now.strftime('%H:%M:%S') return current_time_str def current_date_string_forfile() -> str: - """%Y%m%d""" + """ + Get the current date as a formatted string suitable for filenames. + + The format of the string is "%Y%m%d". + + Returns + ------- + str + The current date as a string suitable for use in filenames. + """ + # "ChatGPT 4o" assisted with generating this docstring. now = datetime.now() current_date_str = now.strftime('%Y%m%d') return current_date_str def current_time_string_forfile() -> str: - """%H%M%S""" + """ + Get the current time as a formatted string suitable for filenames. + + The format of the string is "%H%M%S". + + Returns + ------- + str + The current time as a string suitable for use in filenames. + """ + # "ChatGPT 4o" assisted with generating this docstring. now = datetime.now() current_time_str = now.strftime('%H%M%S') return current_time_str def current_date_time_string_forfile() -> str: - """%Y%m%d_%H%M%S""" + """ + Get the current date and time as a formatted string suitable for filenames. + + The format of the string is "%Y%m%d_%H%M%S". + + Returns + ------- + str + The current date and time as a string suitable for use in filenames. + """ + # "ChatGPT 4o" assisted with generating this docstring. return current_date_string_forfile() + '_' + current_time_string_forfile() def current_time() -> float: + """ + Get the current time in seconds since the epoch. + + Returns + ------- + float + The current time in seconds since the epoch (Unix time). + """ + # "ChatGPT 4o" assisted with generating this docstring. return time.time() def elapsed_time_since_start_sec(start_time: float) -> float: + """ + Calculate the elapsed time in seconds since a given start time. + + Parameters + ---------- + start_time : float + The start time in seconds since the epoch. + + Returns + ------- + float + The elapsed time in seconds since the start time. + """ + # "ChatGPT 4o" assisted with generating this docstring. return time.time() - start_time @@ -135,6 +242,48 @@ def tz(name_or_offset: str | float | timedelta): def add_seconds_to_ymdhmsz( ymdhmsz: list[int, int, int, int, int, float, int], time_sec: float, ignore_legacy=None ) -> list[int, int, int, int, int, float, int]: + """ + Add a specified number of seconds to a date and time represented as a list. + + The input list is expected to be in the format: + [year, month, day, hour, minute, second, zone]. + + Parameters + ---------- + ymdhmsz : list[int, int, int, int, int, float, int] + A list representing the date and time, where: + - year (int): The year. + - month (int): The month (1-12). + - day (int): The day of the month (1-31). + - hour (int): The hour (0-23). + - minute (int): The minute (0-59). + - second (float): The second (0-59.999...). + - zone (int): The time zone offset. + time_sec : float + The number of seconds to add to the date and time. + ignore_legacy : bool, optional + If set to False, a warning message will be printed indicating that this function is a legacy function. + + Returns + ------- + list[int, int, int, int, int, float, int] + A new list representing the updated date and time after adding the specified seconds. + + Raises + ------ + AssertionError + If rolling over a day boundary is not implemented. + + Notes + ----- + This function currently does not handle rolling over a day boundary or month boundary. + + Examples + -------- + >>> add_seconds_to_ymdhmsz([2023, 3, 15, 12, 30, 45.0, 0], 30) + [2023, 3, 15, 12, 31, 15.0, 0] + """ + # "ChatGPT 4o" assisted with generating this docstring. ignore_legacy = tdt_ignore_legacy if (ignore_legacy == None) else ignore_legacy if ignore_legacy == False: print( @@ -173,6 +322,48 @@ def add_seconds_to_ymdhmsz( def subtract_seconds_from_ymdhmsz( ymdhmsz: list[int, int, int, int, int, float, int], time_sec: float, ignore_legacy=None ) -> list[int, int, int, int, int, float, int]: + """ + Subtract a specified number of seconds from a date and time represented as a list. + + The input list is expected to be in the format: + [year, month, day, hour, minute, second, zone]. + + Parameters + ---------- + ymdhmsz : list[int, int, int, int, int, float, int] + A list representing the date and time, where: + - year (int): The year. + - month (int): The month (1-12). + - day (int): The day of the month (1-31). + - hour (int): The hour (0-23). + - minute (int): The minute (0-59). + - second (float): The second (0-59.999...). + - zone (int): The time zone offset. + time_sec : float + The number of seconds to subtract from the date and time. + ignore_legacy : bool, optional + If set to False, a warning message will be printed indicating that this function is a legacy function. + + Returns + ------- + list[int, int, int, int, int, float, int] + A new list representing the updated date and time after subtracting the specified seconds. + + Raises + ------ + AssertionError + If rolling over a month boundary is not implemented. + + Notes + ----- + This function currently does not handle rolling over a month boundary. + + Examples + -------- + >>> subtract_seconds_from_ymdhmsz([2023, 3, 15, 12, 30, 45.0, 0], 30) + [2023, 3, 15, 12, 30, 15.0, 0] + """ + # "ChatGPT 4o" assisted with generating this docstring. ignore_legacy = tdt_ignore_legacy if (ignore_legacy == None) else ignore_legacy if ignore_legacy == False: print( diff --git a/opencsp/common/lib/tool/tk_tools.py b/opencsp/common/lib/tool/tk_tools.py index 29fb5aae6..dbde0d676 100644 --- a/opencsp/common/lib/tool/tk_tools.py +++ b/opencsp/common/lib/tool/tk_tools.py @@ -4,7 +4,40 @@ class TkToolTip(object): + """ + A tooltip class for Tkinter widgets. + + This class provides a tooltip that appears when the mouse hovers over a specified widget. + The tooltip displays a text message and automatically hides when the mouse leaves the widget. + + Attributes + ---------- + waittime : int + The time in milliseconds to wait before showing the tooltip. + wraplength : int + The maximum width of the tooltip in pixels. + id_ : int or None + The identifier for the scheduled tooltip display. + tw : tk.Toplevel or None + The tooltip window instance. + """ + + # "ChatGPT 4o" assisted with generating this docstring. def __init__(self, widget, text='widget info'): + """ + A tooltip class for Tkinter widgets. + + This class provides a tooltip that appears when the mouse hovers over a specified widget. + The tooltip displays a text message and automatically hides when the mouse leaves the widget. + + Parameters + ---------- + widget : tk.Widget + The Tkinter widget to which the tooltip is attached. + text : str, optional + The text to display in the tooltip. Default is 'widget info'. + """ + # "ChatGPT 4o" assisted with generating this docstring. self.waittime = 500 # miliseconds self.wraplength = 180 # pixels self.widget = widget @@ -16,23 +49,33 @@ def __init__(self, widget, text='widget info'): self.tw = None def enter(self, event=None): + """Schedule the tooltip to be displayed.""" + # "ChatGPT 4o" assisted with generating this docstring. self.schedule() def leave(self, event=None): + """Unschedule the tooltip and hide it.""" + # "ChatGPT 4o" assisted with generating this docstring. self.unschedule() self.hidetip() def schedule(self): + """Schedule the tooltip to be shown after the wait time.""" + # "ChatGPT 4o" assisted with generating this docstring. self.unschedule() self.id_ = self.widget.after(self.waittime, self.showtip) def unschedule(self): + """Cancel the scheduled tooltip display.""" + # "ChatGPT 4o" assisted with generating this docstring. id_ = self.id_ self.id_ = None if id_: self.widget.after_cancel(id_) def showtip(self, event=None): + """Create and display the tooltip window.""" + # "ChatGPT 4o" assisted with generating this docstring. x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() + self.widget.winfo_width() y += self.widget.winfo_rooty() + self.widget.winfo_height() @@ -53,6 +96,8 @@ def showtip(self, event=None): label.pack(ipadx=1) def hidetip(self): + """Hide the tooltip window.""" + # "ChatGPT 4o" assisted with generating this docstring. tw = self.tw self.tw = None if tw: @@ -60,17 +105,41 @@ def hidetip(self): def window(*vargs, TopLevel=False, **kwargs): - """Initializes and returns a new tkinter.Tk (or tkinter.TopLevel) instance. - - If creating the window fails, tries again (up to two more times). - - Sometimes initializing a tk window fails. But if you try to - initialize the window again, it seems to always succeed. - When it fails, it is often with an error about not being able to find a - file. Something like:: - - _tkinter.TclError: Can't find a usable init.tcl in the following directories """ + Initialize and return a new Tkinter window or Toplevel instance. + + This function attempts to create a Tkinter window. If the creation fails, it retries up to two more times. + This is useful in cases where initializing a Tkinter window may fail due to environmental issues. + + Parameters + ---------- + *vargs : tuple + Positional arguments to pass to the Tkinter window constructor. + TopLevel : bool, optional + If True, create a Toplevel window instead of a Tk window. Default is False. + **kwargs : dict + Keyword arguments to pass to the Tkinter window constructor. + + Returns + ------- + tk.Tk or tk.Toplevel + A new Tkinter window or Toplevel instance. + + Raises + ------ + Exception + If the window creation fails after three attempts. + + Notes + ----- + If the window creation fails, a warning is logged, and the function will wait for a second before retrying. + + Examples + -------- + >>> main_window = window() + >>> top_window = window(TopLevel=True) + """ + # "ChatGPT 4o" assisted with generating this docstring. window_class = tk.Tk if TopLevel: window_class = tk.Toplevel diff --git a/opencsp/common/lib/tool/unit_conversion.py b/opencsp/common/lib/tool/unit_conversion.py index 07337e178..f95cd0819 100755 --- a/opencsp/common/lib/tool/unit_conversion.py +++ b/opencsp/common/lib/tool/unit_conversion.py @@ -11,17 +11,101 @@ # Inch <--> Meters def inch_to_meter(value_inch: float) -> float: + """ + Convert a value in inches to meters. + + Parameters + ---------- + value_inch : float + The value in inches to be converted. + + Returns + ------- + float + The equivalent value in meters. + + Examples + -------- + >>> inch_to_meter(1.0) + 0.0254 + >>> inch_to_meter(12.0) + 0.3048 + """ + # "ChatGPT 4o" assisted with generating this docstring. return value_inch * M_PER_INCH def meter_to_inch(value_meter: float) -> float: + """ + Convert a value in meters to inches. + + Parameters + ---------- + value_meter : float + The value in meters to be converted. + + Returns + ------- + float + The equivalent value in inches. + + Examples + -------- + >>> meter_to_inch(0.0254) + 1.0 + >>> meter_to_inch(0.3048) + 12.0 + """ + # "ChatGPT 4o" assisted with generating this docstring. return value_meter / M_PER_INCH # Dots per Inch <--> Dots per Meters def dpi_to_dpm(value_dpi: float) -> float: + """ + Convert a value in dots per inch (DPI) to dots per meter (DPM). + + Parameters + ---------- + value_dpi : float + The value in dots per inch to be converted. + + Returns + ------- + float + The equivalent value in dots per meter. + + Examples + -------- + >>> dpi_to_dpm(300.0) + 11811.023622047244 + >>> dpi_to_dpm(72.0) + 2834.645669291338 + """ + # "ChatGPT 4o" assisted with generating this docstring. return value_dpi / M_PER_INCH def dpm_to_dpi(value_dpi: float) -> float: + """ + Convert a value in dots per meter (DPM) to dots per inch (DPI). + + Parameters + ---------- + value_dpm : float + The value in dots per meter to be converted. + + Returns + ------- + float + The equivalent value in dots per inch. + + Examples + -------- + >>> dpm_to_dpi(11811.023622047244) + 300.0 + >>> dpm_to_dpi(2834.645669291338) + 72.0 + """ + # "ChatGPT 4o" assisted with generating this docstring. return value_dpi * M_PER_INCH diff --git a/opencsp/test/test_DocStringsExist.py b/opencsp/test/test_DocStringsExist.py index 5ecd6e908..f71853046 100644 --- a/opencsp/test/test_DocStringsExist.py +++ b/opencsp/test/test_DocStringsExist.py @@ -46,6 +46,7 @@ import opencsp.common.lib.render_control.RenderControlPowerpointPresentation import opencsp.common.lib.render_control.RenderControlTrajectoryAnalysis import opencsp.common.lib.render_control.RenderControlVideoTracks +import opencsp.common.lib.tool.dict_tools def test_docstrings_exist_for_methods(): @@ -310,6 +311,23 @@ def test_docstrings_exist_for_methods(): opencsp.common.lib.target.target_image, ] + tool_class_list = [ + opencsp.common.lib.tool.dict_tools, + opencsp.common.lib.tool.exception_tools, + opencsp.common.lib.tool.file_tools, + opencsp.common.lib.tool.hdf5_tools, + opencsp.common.lib.tool.image_tools, + opencsp.common.lib.tool.list_tools, + opencsp.common.lib.tool.log_tools, + opencsp.common.lib.tool.math_tools, + opencsp.common.lib.tool.string_tools, + opencsp.common.lib.tool.system_tools, + opencsp.common.lib.tool.time_date_tools, + opencsp.common.lib.tool.tk_tools, + opencsp.common.lib.tool.typing_tools, + opencsp.common.lib.tool.unit_conversion, + ] + common_class_list = ( camera_class_list + csp_class_list @@ -324,6 +342,7 @@ def test_docstrings_exist_for_methods(): + render_class_list + render_control_class_list + common_target_class_list + + tool_class_list ) class_list = app_class_list + common_class_list