6
\$\begingroup\$

I have created a wrapper for displaying messages with ArcPy and IDLE (as that is all I have available to myself due to certain circumstances) as I frequently use both to test and develop new tools. However I find myself repeatingprint statements andarcpy functions to display feedback.

Unfortunately, it has to be in compatible with Python 2.7.

"""REMOVED HEADER"""import sysfrom time import strftimeimport arcpydef display_title(title, char_limit=100):    # Format the title, and provide a border above and below to seperate the    # title    title = ("\n{0}\n{1:^" + str(char_limit) + "}\n{0}\n").format(        "-" * char_limit, title.upper())    # Display the title    print(title)    arcpy.AddMessage(title)def display_message(message, char_limit=100):    # Add the time to the message, and this will also run the    # beautify_message function    message = add_time_to_message(message, char_limit)    # Display the message    print(message)    arcpy.AddMessage(message)def display_warning(message, char_limit=100):    # Add the time to the message, and this will also run the    # beautify_message function    message = add_time_to_message("WARNING: " + message, char_limit)    # Display the message    print(message)    arcpy.AddWarning(message)def display_error(message, char_limit=100, traceback=False):    # Add the time to the message, and this will also run the    # beautify_message function    message = add_time_to_message("Error" + message, char_limit)    # Add the Python traceback information (if available)    if traceback:        tracebackInfo = traceback.format_exc()        if tracebackInfo != "None\n":            message += "\n\nPython Traceback Information:\n"            message += tracebackInfo    # Display the message    print(message)    arcpy.AddError(message)    # Terminate the Script now    sys.exit()def display_parameters(parameters, char_limit=100):    # Make sure that the input type is correct    if isinstance(parameters, tuple) or isinstance(parameters, list):        if len(parameters) > 1:            # Then there is more than one, print a list            var_string = "Parameter list:\n"            for var in parameters:                var_string += "{} = {}\n".format(var[0], var[1])            # Remove the last "\n"            var_string[:-1]        else:            var_string = "{} = {}".format(parameters[0][0], parameters[0][1])        # Format the message string        message = add_time_to_message(var_string, char_limit)        # Display the message        print(message)        arcpy.AddMessage(message)# Format the message so it displays nicely within the progress boxdef beautify_message(message, char_limit=100):    # Create an empty list of beautified lines    processed_lines = []    # Split the message into lines that are defined with a new line    split_message = message.split("\n")    # Loop through the each split line    for line in split_message:        # Set the counter to zero        counter = 0        # Loop though each split line to check the length        while len(line) > char_limit:            # Then this line is too long, split it up            split_line = line[counter:(counter + 1) * char_limit]            # Capture the last space just in case            space = True if split_line[-1] == " " else 0            # Split it on spaces            split_line = split_line.split()            # Add the chunk to lines            processed_lines.append(" ".join(split_line[:-1]))            # Take the left over bit and add it back to line            line = split_line[-1] + " " * space + line[(                counter + 1) * char_limit:]        # Add the last bit to lines        processed_lines.append(line)    # Return the final output    return processed_lines# Add the timestamp and indent messagedef add_time_to_message(message, char_limit=100):    # Determine the current time in string format to be included in some     # messages    current_time = strftime("%x %X") + " - "    # Determine the limit for each line if including the time    limit = char_limit - len(current_time)    # Break the message up into lines of the right length    lines = beautify_message(message, limit)    # Add the time and the first line    message = current_time + lines[0] + "\n"    # Add the rest of the lines    for line in lines[1:]:        message += " " * len(current_time) + line + "\n"    # Remove the extra "\n" character    message = message[:-1]    # Return the final output    return message

Any and all constructive criticism welcome!

AlexV's user avatar
AlexV
7,3632 gold badges24 silver badges47 bronze badges
askedDec 9, 2019 at 18:16
\$\endgroup\$

1 Answer1

3
\$\begingroup\$

Overall, your code is well-formatted and I believe is fairly readable. It clearly accomplishes its job, looks nice! I'll keep this review in python 2.7 since it sounds like your constraints require it, though hopefully you are able to migrate it to 3.x eventually:

Multipleisinstance checks

Instead of:

if isinstance(val, type) or isinstance(val, othertype) or ...

Do

if isinstance(val, (type, othertype, ...))

Guard Clauses

Switch the instance check to a negative one and return early. This removes indentation from your code:

# go fromif isinstance(parameters, (tuple, list)):    # code# doif not isinstance(parameters, (tuple, list)):    return# rest of function

It also seems strange that if parameters don't match a type, that a function would silently return rather than raise or at least display an error. I'd reconsider the implementation here:

if not isinstance(parameters, (tuple, list)):    raise TypeError(        "Parameters expected to be of type 'list' or 'tuple', got {}"        .format(type(parameters))    )

display_parameters

There are a few things to note here:

  1. Do a single concatenation of strings rather than multiple in a loop
  2. Use tuple unpacking where possible
def display_parameters(parameters, char_limit=100):    if not isinstance(parameters, (tuple, list)):        raise TypeError(...)    if len(parameters) > 1:        var_string = "Parameter list:\n"    else:        var_string = ""    var_string += (        "\n".join(                "{} = {}".format(a, b) for a, b, *_ in parameters        )    ).rstrip('\n')    # Format the message string    message = add_time_to_message(var_string, char_limit)    # Display the message    print(message)    arcpy.AddMessage(message)

beautify_message

This is probably the most confusing function IMO. It's well named, so I understand the intent, but it reads a bit clunky. Thecounter is really a static0, since it's never redefined.

Furthermore, in trying to break up lines into n-length chunks, this is thegrouper recipedefined in more-itertools (with the options that fit your use-case the best):

from itertools import izip_longestdef grouper(iterable, n, fillvalue=''):    "Collect data into non-overlapping fixed-length chunks or blocks."    # grouper('ABCDEFG', 3) → ABC DEF G    iterators = [iter(iterable)] * n    return izip_longest(fillvalue=fillvalue, *iterators)

Replacing your while-loop with this code:

# Format the message so it displays nicely within the progress boxdef beautify_message(message, char_limit=100):    processed_lines = []    for line in message.split('\n'):        for grp in grouper(line, char_limit):            processed_lines.append(''.join(grp))    # Return the final output    return processed_lines

Docstrings

I would add docstrings to show expected inputs, returns, and general documentation of your code.

answeredJul 24, 2024 at 17:15
C.Nivs's user avatar
\$\endgroup\$

You mustlog in to answer this question.