2
\$\begingroup\$

I am building a CLI that wraps an API for work. The CLI is being build to handle the "common" tasks users can perform with the API so that they don't need to get into the details. If they want to do more, they are able to but have to use the API.

The problem I'm running into is that it seems very inefficient to pluck out only the fields I want from the API and list them individually. I have two types of results in the CLI.

Result 1 is a detailed view of a single item. port contains a JSON object that was returned by the API. The fields I have listed, as the only ones I care about. The JSON object is much larger than these few fields.

port_details = [
    ["Interface ID", port["port_id"]],
    ["Description", port["description"]],
    ["Market", port["location"]["market"]["market_code"]],
    ["Site", port["location"]["site"]["site_name"]],
    ["Time Created", port["created"]],
    ["Time Updated", port["updated"]],
    ["Device Name", port["device_data"]["device_name"]],
    ["Interface Speed", port["device_data"]["speed"]],
    ["Status", port["state"]],
]
headers = ["", "Value"]
port_table = show_table(
    ctx,
    port_details,
    column_headers=headers,
    max_width=250,
)
click.echo(port_table)

This results in a table that looks like this:

                   Value                                    
------------------ ------------------------------------------
Interface ID       IF-LAB1-7785421
Description        Test Port LAB1
Market             LAB1
Site               LAB1a
Time Created       2019-05-14T11:10:09-05:00
Time Updated       2019-05-14T11:10:16-05:00
Device Name        lab1.sw1
Interface Speed    1G
Status             Active

The second result is a table of items. You'd use this view to see an overview and then you can drill down for details on a specific item using the above.

circuit_details = [
    [
        circuit["circuit_id"],
        circuit["description"],
        circuit["vlan_id"],
        circuit["status"],
        circuit["attr"]["limit_in"]
        circuit["attr"]["limit_out"]
    ]
    for circuit in circuits
]

circuit_headers = [
    "Circuit ID",
    "Description",
    "VLAN",
    "Status",
    "Rate Limit In",
    "Rate Limit Out",
]

circuit_table = show_table(
    ctx,
    circuit_details,
    column_headers=circuit_headers,
    max_width=250,
)
click.echo(circuit_table)

This results in a table like this:

 Circuit ID              Description          VLAN   Status   Rate Limit In   Rate Limit Out 
----------------------- -------------------- ------ -------- --------------- ----------------
 CIRCUIT-LAB1-LAB2-842   Test Circuit 1          5   active       1000             1000
 CIRCUIT-LAB1-LAB2-846   Test Circuit 2          6   active        250              250

Both of these are using a call to show_tables which uses the Python BeautifulTable package:

def show_table(
    ctx,
    data,
    column_headers=[],
    good_values=[],
    bad_values=[],
    sort_column=None,
    max_width=200,
    alignments={},
):
    """
    Create a table of data with optional headers, style, and highlighted values

    The table will utilize the default printing style and will highlight all
    values in the table that are in `good_values` as the color GREEN and all
    values in the table that are in `bad_values` as the color RED.

    Parameters
    ----------
    ctx: context object, required
        The click context object (holds the table style)
    data: list of lists, required
        Each sublist must contain the values for a single row in the table
    column_headers: list, optional
        A list of the table headers
    max_width: integer, optional
        Set the width of the table. Defaults to 200 characters
    """
    table = BeautifulTable(max_width=max_width)
    if column_headers:
        table.column_headers = column_headers
    table.set_style(TableStyle[ctx.obj["PRINT_TABLE"]].value)
    for row in data:
        for idx, val in enumerate(row):
            if str(val).lower() in good_values:
                row[idx] = click.style(str(val), fg="green")
            if str(val).lower() in bad_values:
                row[idx] = click.style(str(val), fg="red")
        table.append_row(row)
    if sort_column:
        table.sort(sort_column)
    for column, align in alignments.items():
        table.column_alignments[column] = ColumnAlignment[align].value

    return table

I am looking for ways to improve the listing of table rows (the *_details variables that are lists of lists). I have some subcommands that list 20-30 JSON data points. My code seems like massive lists - one for the data and one for the column headers - and then a call to show_tables.

Is there a better way to do this and eliminate these massive lists?

\$\endgroup\$
  • \$\begingroup\$ Do you have code that makes these detail lists? If so you should provide the code, otherwise it's impossible to accurately improve code we don't have access to. You've also tagged this performance, what is most important to you improving the detail lists or improving the performance of show_table? \$\endgroup\$ – Peilonrayz Jun 9 at 1:32
  • \$\begingroup\$ @Peilonrayz No, I do not have a dedicated function to create these lists. Since each API call returns a different set of fields, I've manually built these lists for my CLI. So, either I have the lists here, or I image I supply a list to a function that says "take these fields from an API response" - which isn't a horrible idea - I still have these large lists. I am not unhappy with the performance of show_table. I am unhappy with the lists themselves. \$\endgroup\$ – NewGuy Jun 10 at 2:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.