From 398005daf5332c87529c1cc78a64f710330c9c95 Mon Sep 17 00:00:00 2001 From: william cross Date: Tue, 22 Oct 2024 15:47:59 +0100 Subject: [PATCH] Updated click function to send functionality to other functions moved user error checking to another function updated that formatting function to use SystemExit and and added "Error:" to the front of two of the messages updated the pytests to account for the error message and code success changes moved create message to another function added a function to turn the monitor models into dictionaries to make it easier in the FastAPI web page updated the complex and simple prints for the dictionary --- nlds_utils/nlds_monitor.py | 327 +++++++++++++++++++------- tests/nlds_utils/test_nlds_monitor.py | 16 +- 2 files changed, 246 insertions(+), 97 deletions(-) diff --git a/nlds_utils/nlds_monitor.py b/nlds_utils/nlds_monitor.py index 11eaf892..50fe2ad1 100644 --- a/nlds_utils/nlds_monitor.py +++ b/nlds_utils/nlds_monitor.py @@ -90,59 +90,246 @@ def query_monitor_db( return trec -def print_simple_monitor(record_list, stat_string): +def print_simple_monitor(records_dict_list, stat_string): """Print a multi-line set of status for monitor""" click.echo(stat_string) click.echo( f"{'':<4}{'user':<16}{'group':<16}{'id':<6}{'action':<16}{'job label':<16}" f"{'state':<23}{'last update':<20}" ) - for record in record_list: - state = record.get_state() - if "job_label" in record.__dict__ and record.__dict__["job_label"]: - job_label = record.job_label - else: - job_label = "" + for record in records_dict_list: click.echo( - f"{'':<4}{record.user:<16}{record.group:<16}{record.id:<6}" - f"{record.api_action:<16}{job_label:16}{state.name:<23}" - f"{(record.creation_time)}" + f"{'':<4}{record['user']:<16}{record['group']:<16}{record['id']:<6}" + f"{record['api_action']:<16}{record['job_label']:16}{record['state']:<23}" + f"{(record['creation_time'])}" ) -def print_complex_monitor(record_list, stat_string): +def print_complex_monitor(records_dict_list, stat_string): """Print a multi-line set of status for monitor in more detail, with a list of failed files if necessary""" click.echo(stat_string) - for record in record_list: + for record in records_dict_list: click.echo("") - click.echo(f"{'':<4}{'id':<16}: {record.id}") - click.echo(f"{'':<4}{'user':<16}: {record.user}") - click.echo(f"{'':<4}{'group':<16}: {record.group}") - click.echo(f"{'':<4}{'action':<16}: {record.api_action}") - click.echo(f"{'':<4}{'transaction id':<16}: {record.transaction_id}") - click.echo(f"{'':<4}{'creation time':<16}: {(record.creation_time)}") - state = record.get_state() - click.echo(f"{'':<4}{'state':<16}: {state.name}") - if "warnings" in record.__dict__: - warn_str = "" - for w in record.warnings: - warn_str += w.warning + f"\n{'':<22}" - click.echo(f"{'':<4}{'warnings':<16}: {warn_str[:-23]}") + click.echo(f"{'':<4}{'id':<16}: {record['id']}") + click.echo(f"{'':<4}{'user':<16}: {record['user']}") + click.echo(f"{'':<4}{'group':<16}: {record['group']}") + click.echo(f"{'':<4}{'action':<16}: {record['api_action']}") + click.echo(f"{'':<4}{'transaction id':<16}: {record['transaction_id']}") + click.echo(f"{'':<4}{'creation time':<16}: {(record['creation_time'])}") + click.echo(f"{'':<4}{'state':<16}: {record['state']}") + + warn_str = "" + for w in record["warnings"]: + warn_str += w["warning"] + f"\n{'':<22}" + click.echo(f"{'':<4}{'warnings':<16}: {warn_str[:-23]}") click.echo(f"{'':<4}{'sub records':<16}->") - for sr in record.sub_records: - sr_dict = sr.__dict__ - click.echo(f"{'':4}{'+':<4} {'id':<13}: {sr.id}") - click.echo(f"{'':<9}{'sub_id':<13}: {sr.sub_id}") - click.echo(f"{'':<9}{'state':<13}: {sr.state.name}") - click.echo(f"{'':<9}{'last update':<13}: {(sr.last_updated)}") - - if len(sr_dict["failed_files"]) > 0: + for sr in record["sub_records"]: + click.echo(f"{'':4}{'+':<4} {'id':<13}: {sr['id']}") + click.echo(f"{'':<9}{'sub_id':<13}: {sr['sub_id']}") + click.echo(f"{'':<9}{'state':<13}: {sr['state']}") + click.echo(f"{'':<9}{'last update':<13}: {(sr['last_updated'])}") + + if len(sr["failed_files"]) > 0: click.echo(f"{'':<9}{'failed files':<13}->") - for ff in sr.failed_files: - click.echo(f"{'':<9}{'+':<4} {'filepath':<8} : {ff.filepath}") - click.echo(f"{'':<9}{'':>4} {'reason':<8} : {ff.reason}") + for ff in sr["failed_files"]: + click.echo(f"{'':<9}{'+':<4} {'filepath':<8} : {ff['filepath']}") + click.echo(f"{'':<9}{'':>4} {'reason':<8} : {ff['reason']}") + + +def validate_inputs(start_time, end_time, state, record_state): + """Ensures user inputs are valid before querying the database""" + try: + if start_time and end_time: + if start_time > end_time: + raise ValueError("Error: Start time must be before end time.") + + if state: + try: + state = State[state.upper()] + except KeyError: + raise ValueError(f"Error: Invalid state: {state}") + + if record_state: + try: + record_state = State[record_state.upper()] + except KeyError: + raise ValueError(f"Error: Invalid state: {record_state}") + except ValueError as e: + raise SystemExit(e) + return state, record_state + + +def construct_stat_string( + id, + transaction_id, + user, + group, + state, + record_state, + start_time, + end_time, + order, +): + """ + Constructs a status string based on the inputs and prints the response. + """ + details = [] + + # Construct details based on provided values + stat_string = "State of transactions for " + if id: + details.append(f"id: {id}") + elif transaction_id: + details.append(f"transaction id: {transaction_id}") + else: + if user: + details.append(f"user: {user}") + if group: + details.append(f"group: {group}") + if state: + details.append(f"state: {state.name}") + if record_state: + details.append(f"record state: {record_state.name}") + if start_time and end_time: + details.append(f"between: {start_time} and {end_time}") + elif start_time and not end_time: + details.append(f"from: {start_time}") + + # If no details were added, set req_details to "all records" + if not details: + req_details = "all records" + else: + # Join the provided details with commas + req_details = ", ".join(details) + + # Add order to the request details + if order: + req_details += f", order: {order}" + + stat_string += req_details + + return stat_string + + +def construct_record_dict( + query, +): + """ + Turn the monitor models into a large list of dictionarys to make it easier + for the fastAPI web page + """ + records_dict = [] + + for record in query: + # Create an empty dictionary for the TransactionRecord + record_dict = {} + + # Specify the order of fields + field_order = [ + "id", + "transaction_id", + "user", + "group", + "job_label", + "api_action", + "creation_time", + "state", + "warnings", + "sub_records", + ] + + for key in field_order: + if key == "state": + state = record.get_state() + record_dict[key] = state.name + else: + if hasattr(record, key): + value = getattr(record, key) + record_dict[key] = value + + # Convert sub_records to dictionaries + if record.sub_records: + record_dict["sub_records"] = [] + for sub_record in record.sub_records: + # Create an empty dictionary for the SubRecord + sub_record_dict = {} + sub_field_order = [ + "id", + "sub_id", + "state", + "retry_count", + "last_updated", + "failed_files", + "transaction_record_id", + ] # Specify sub_record field order + + for key in sub_field_order: + if hasattr(sub_record, key): + value = getattr(sub_record, key) + if key == "state": + sub_record_dict[key] = value.name + else: + sub_record_dict[key] = value + + # Convert failed_files to dictionaries + if sub_record.failed_files: + sub_record_dict["failed_files"] = [] + for failed_file in sub_record.failed_files: + failed_file_dict = {} + failed_field_order = [ + "id", + "filepath", + "reason", + "sub_record_id", + ] # Specify failed_file field order + + for key in failed_field_order: + if hasattr(failed_file, key): + value = getattr(failed_file, key) + failed_file_dict[key] = value + + sub_record_dict["failed_files"].append(failed_file_dict) + else: + sub_record_dict["failed_files"] = ( + [] + ) # If there are no failed_files, assign an empty list + + # Append the constructed sub_record_dict to the sub_records list + record_dict["sub_records"].append(sub_record_dict) + + else: + record_dict["sub_records"] = ( + [] + ) # If there are no sub_records, assign an empty list + + # Convert warnings to dictionaries + if record.warnings: + record_dict["warnings"] = [] + for warning in record.warnings: + warning_dict = {} + warning_field_order = [ + "id", + "warning", + "transaction_record_id", + ] # Specify warning field order + + for key in warning_field_order: + if hasattr(warning, key): + value = getattr(warning, key) + warning_dict[key] = value + + record_dict["warnings"].append(warning_dict) + else: + record_dict["warnings"] = ( + [] + ) # If there are no warnings, assign an empty list + + # Append the constructed record_dict to records_dict + records_dict.append(record_dict) + + return records_dict @click.command() @@ -237,24 +424,7 @@ def view_jobs( else: order = "descending" - if start_time and end_time: - if start_time > end_time: - click.echo("Error: Start time must be before end time.") - exit() - - if state: - try: - state = State[state.upper()] - except KeyError: - click.echo(f"Invalid state: {state}") - exit() - - if record_state: - try: - record_state = State[record_state.upper()] - except KeyError: - click.echo(f"Invalid state: {record_state}") - exit() + state, record_state = validate_inputs(start_time, end_time, state, record_state) # Connect to the monitor database session = connect_to_monitor() @@ -271,45 +441,24 @@ def view_jobs( order, ) - details = [] - - # Check if each value is provided and add to details - stat_string = "State of transactions for " - if id: - details.append(f"id: {id}") - elif transaction_id: - details.append(f"transaction id: {transaction_id}") - else: - if user: - details.append(f"user: {user}") - if group: - details.append(f"group: {group}") - if state: - details.append(f"state: {state.name}") - if record_state: - details.append(f"record state: {record_state.name}") - if start_time and end_time: - details.append(f"between: {start_time} and {end_time}") - if start_time and not end_time: - details.append(f"from: {start_time}") - - # If no details were added, set req_details to 'all records' - if not details: - req_details = "all records" - else: - # Join the provided details with commas - req_details = ", ".join(details) - - # Add the order - if order: - req_details += f", order: {order}" + stat_string = construct_stat_string( + id, + transaction_id, + user, + group, + state, + record_state, + start_time, + end_time, + order, + ) - stat_string += req_details + records_dict_list = construct_record_dict(query) if complex or id or transaction_id: - print_complex_monitor(query, stat_string) + print_complex_monitor(records_dict_list, stat_string) else: - print_simple_monitor(query, stat_string) + print_simple_monitor(records_dict_list, stat_string) if __name__ == "__main__": diff --git a/tests/nlds_utils/test_nlds_monitor.py b/tests/nlds_utils/test_nlds_monitor.py index 8a67b7b6..429d694a 100644 --- a/tests/nlds_utils/test_nlds_monitor.py +++ b/tests/nlds_utils/test_nlds_monitor.py @@ -454,10 +454,10 @@ def test_incorrect_state(setup_test, start_time): ], ) - expected_output = "Invalid state: fail" + expected_output = "Error: Invalid state: fail" - # Assert no errors - assert result.exit_code == 0 + # Assert has an error + assert result.exit_code == 1 # Assert the output from click.echo matches expectations assert expected_output in result.output @@ -588,10 +588,10 @@ def test_incorrect_record_state(setup_test, start_time): "success", ], ) - expected_output = "Invalid state: success" + expected_output = "Error: Invalid state: success" - # Assert no errors - assert result.exit_code == 0 + # Assert has an error + assert result.exit_code == 1 # Assert the output from click.echo matches expectations assert expected_output in result.output @@ -901,8 +901,8 @@ def test_time_endtime_before_start_time(setup_test): expected_output = "Error: Start time must be before end time." - # Assert no errors - assert result.exit_code == 0 + # Assert has an error + assert result.exit_code == 1 # Assert the output from click.echo matches expectations assert expected_output in result.output