Importance of importing, without imploding

2014-04-02 UPDATE: I found a a much better way to solve this problem.

I have spent the better part of a day, and several rounds of digging at Stackoverflow, to figure out the solution to this issue. It was so tricky (for my middle-aged brain at least) that I want to share it with the world in as simple and straightforward a way as possible in the hope I save some fellow newbies some aggravation.

The more I code, and the more problems I solve with code, the more I don’t want to have to solve the same problem more than once. I’ve tasted the elegant efficiency of re-using code. Once I have a piece of code “perfected” I want to put it in a directory that I can access from any of my projects. How can I share code among several projects in different directories? Here’s my example:

I have two project directories and a directory called “tools” that contains a module “coolstuff.py”. All three directories are subdirectories of ‘Projects.’ “coolstuff.py’ contains a function also called “coolstuff.” I want to be able to import and use the “coolstuff” function in either of my project directories.

c:\mypath\projects\ProjectA\spam.py
c:\mypath\projects\ProjectB\eggs.py
c:\mypath\projects\tools\coolstuff.py

Contents of coolstuff.py:

def coolstuff():
    """
    This is a cool function you should use often.
    """
    print ("Cool function activated!")

I want to be able to call “coolstuff()” from either “spam.py” or “eggs.py.” Obviously I need an import statement. I started off with just ‘import coolstuff’, but that didn’t work. I’ll spare you the gory details of what I went through to find this solution, but suffice it to say, it involved just about every version of “from … import ….”, “import….”, etc., so I’ll just skip straight to the answer:

Contents of ‘spam.py’:

import sys
sys.path.append('C:/mypath/projects/tools')
from coolstuff import coolstuff
coolstuff()

The “sys” module allows run-time addition to the “PATH” environmental variable using the ‘sys.path.append’ statement. This allows the ‘import’ statement to find ‘tools’ and whatever is in it.

Since the function I want is ALSO named ‘coolstuff’, I have to reference it to the right of the import statement. When I finally got the path issue solved so that the statement ‘import coolstuff’, by itself, went error free, I got a ‘module object is not callable’ error when I tried to actually use the function. Turns out, of course, that the FILE ‘coolstuff’ is the ‘module object’, and the FUNCTION ‘coolstuff’ inside the FILE ‘coolstuff’ has to be called in order to work, hence: ‘from coolstuff import coolstuff.’

For the time being, I prefer to name my modules with the same name as the functions they contain, so that one glance in the ‘tools’ directory is all I need to find the function I want. I prefer to set the “PATH” variable at runtime so I don’t have to monkey with my PC’s environmental settings manually ever time I move code to a different folder.

Hope this clears things up for anyone who was similarly vexed.

A Quest Begins

I recently stumbled across two little applications that, along with my recent advances in figuring out Tkinter, have finally given a much needed boost of inspiration — and a goal to strive for.

First there was this:

phoneapp

This is a step-by-step tutorial for a simple phone list using Tkinter and SQLite.  The user selects a name from the list and presses the “Load” button to load it into the form at the top.  The form is used to make changes to the data; the list is just for viewing and selecting.  This is what I have wanted to accomplish, boiled down to its absolute simplest level.  This little app also answered a big question for me; how do I get data from an SQLite database table into a Tkinter form for viewing and editing?  The short answer: the Tkinter Listbox widget, which has an INSERT method.

Even better, I found this:

inventory

Now we’re talking! Even though it’s a bit buggy, it’s the closest thing I have yet seen to what I have wanted to accomplish. It’s got a system of relational tables: customers, invoices and items. It has forms that are called when the user wants to add or change data. But its most intriguing item is a custom widget called a MultiListbox:
multilist

I found this object fascinating, because it opened my eyes to the power of Tkinter. It’s actually possible to make custom widgets out of the basic ones provided. The MultiListbox is a set of regular Listboxes squashed together in a frame and synchronized. It lines up each data field in its own column. It accepts data input in the form of tuples, for example: (‘Date’,’Item Number’,’Quantity’,’Price’). Each tuple is a row of data in the MultiListbox.

So, now, at last, I’ve found my quest!

holygrail

I plan to build an app around two basic objects: what I call a “TableForm”, for filtering, viewing and selecting data….
TableForm

..and what I have named a “RecordForm”, which will be used to edit, add, delete or update a record…
RecordForm

These two base objects could be duplicated for each table in the underlying SQLite database.

So here’s the short version of my plan. I’m starting with the coding of the TableForm, since it’s really the heart of my app. I have broken the Table Form down into component classes, starting with the MultiListbox (the most complex part). I plan to code and test each object separately, then piece them all together to make the TableForm class. I’ll then repeat this process with the RecordForm. This rudimentary data management setup could be applied to an endless array of applications, from personal finance to getting data from the Web to tracking my running

In true Python spirit, I wanted to name my app “DataQuest”, but found out there is a company called Dataquest. I didn’t want to get in trouble with them or cause confusion, so I’ve decided to call my app “DataQ.” I can already see the most time-consuming part of development is just figuring out how stuff works. When I first saw the code for the MultiListbox, it might as well have been in Chinese. It has taken some in-depth study of my Tkinter links and a lot of trial-and-error experiments with the code to get it deciphered. I feel like one of the great seafaring explorers of the 15th and 16th centuries, crossing a turbulent, uncharted ocean to a new world, or a biologist trying to decode DNA sequences.

See below for what I have so far in my version of the MultiListbox base class, along with comments I wrote in the code that explain the various parts as I was able to figure out what they did.

# A custom widget consisting of multiple Listbox widgets, with each listbox containing a column of data.
# Based on MultiListBox code used by suhailvs@gmail.com (https://sourceforge.net/projects/pyinvoice/)
# The code I was able to figure out is explained in the comments.
# Functions with comment ???? are ones I haven't figured out yet.

from tkinter import *
class MultiListbox(Frame):
    '''MultiListbox made by Labels as table header and Listbox as table columns'''
    def __init__(self, master, lists):
        Frame.__init__(self, master)
        self.lists = [] # A list of listboxes
        self.footers = [] # List of listbox footers
        for l,w in lists: # l=label, w=width for each listbox
            # Each listbox gets its own frame
            frame = Frame(self); frame.pack(side=LEFT, expand=YES, fill=BOTH)

            # Label = listbox name
            Label(frame, text=l, borderwidth=1, relief=RAISED).pack(fill=X)

            # Create each listbox
            lb = Listbox(frame, width=w, borderwidth=0, selectborderwidth=0,
                 relief=FLAT, exportselection=FALSE)
            lb.pack(expand=YES, fill=BOTH)

            # Add a footer for each listbox. Available for calculated totals.
            footer = Label(frame)
            footer.pack(fill=X)

            # Lists = list of numerical references to each listbox (ex .43982632.44021632.44021744.44022080)
            # Keeps track of listboxes after they are created.
            self.lists.append(lb)
            self.footers.append(footer)

            # Each listbox gets these bindings

            # left mouse button pressed. Required so left click selects entire line.
            lb.bind('', lambda e, s=self: s._select(e.y))

            # double click will eventually be what brings up the transaction edit form.
            lb.bind('', lambda e, s=self: s.double_click())

            # These came with the original MLB code but don't seem to do anything.
                #lb.bind('', lambda e, s=self: s._select(e.y)) # left mouse button pressed while moving
                #lb.bind('', lambda e: 'break') # mouse pointer leaves the widget
                #lb.bind('', lambda e, s=self: s._b2motion(e.x, e.y))
                #lb.bind('', lambda e, s=self: s._button2(e.x, e.y)) #right mouse button pressed

        # The frame that will contain the individual list boxes, each with its own frame (see above).
        frame = Frame(self); frame.pack(side=LEFT, fill=Y)
        Label(frame, borderwidth=1, relief=RAISED).pack(fill=X)
        sb = Scrollbar(frame, orient=VERTICAL, command=self._scroll)
        sb.pack(expand=YES, fill=Y)

        # lists[0] = the first (left most) list box.
        self.lists[0]['yscrollcommand']=sb.set

    def double_click(self):
        print(self.item_selected)

    def _select(self, y):
        row = self.lists[0].nearest(y) # integer index of selected row

        # Calls the selection_clear function to un-select whatever row was selected before left button was clicked.
        self.selection_clear(0, END)

        # Calls the selection_set function to select the line clicked on.
        self.selection_set(row)
        return 'break'

    def _button2(self, x, y): #????
        for l in self.lists: l.scan_mark(x, y)
        return 'break'

    def _b2motion(self, x, y): #????
        for l in self.lists: l.scan_dragto(x, y)
        return 'break'

    def _scroll(self, *args): #????
        for l in self.lists:
            l.yview(*args)

    def curselection(self): #????
        return self.lists[0].curselection()

    def delete(self, first, last=None):
        for l in self.lists:
            l.delete(first, last)

    def get(self, first, last=None):
        result = []
        for l in self.lists:
            result.append(l.get(first,last))
        if last: return list(map(*[None] + result))
        return result

    def index(self, index):
        self.lists[0].index(index)

    def insert(self, index, *elements): #Elements = the fields in a row.

        # Loop through each field in the row
        for e in elements:

            # Each field will have an index number. Ex: e[0] = ID, e[1] = Date, etc.
            i = 0
            for l in self.lists:
                # Insert each field in its applicable list box
                # Insert a blank if the field is None, otherwise boxes won't align.
                if e[i] == None:
                    l.insert(index, " ")
                else:
                    l.insert(index, e[i])
                i = i + 1

    def size(self):
        return self.lists[0].size()

    def see(self, index):
        for l in self.lists:
            l.see(index)

    def selection_anchor(self, index):
        for l in self.lists:
            l.selection_anchor(index)

    def selection_clear(self, first, last=None):
        # Unselects the entire line by looping over each of the list boxes that make up the MLB.
        for l in self.lists:
            l.selection_clear(first, last)

    def selection_includes(self, index):
        return self.lists[0].selection_includes(index)

    def selection_set(self, first, last=None):
        # first = the index of the selected row

        # self.item_selected = a list whose 1st item is the index of the row, followed by the entire row.
        # Example:
        # [6,134,'2014-02-03','Publix','',188,'$0.00','$3.48',197,1]

        self.item_selected=[first,]+self.get(first)

        for l in self.lists:
            # Sets the selection to the same row in all list boxes
            l.selection_set(first, last)

    def not_focus(self):
        for l in self.lists:
            l['takefocus']=False

    def calc_total(self,column):
        # Returns the total of the selected column number. Column must contain numeric values only.
        total = 0
        for n in range(0,self.lists[column].size()):
            total = total + float(self.lists[column].get(n))
        return total

    def footer_val(self,column,value):
        # Sets the text to be displayed in the footer of the column specified.
        v = StringVar()
        self.footers[column].config(textvariable=v)
        v.set(value)

If you scrolled all the way through that huge block of code to get here, I’ll bet you are the kind of person who sits in a movie theater and waits for all the credits to finish, hoping for something fun or entertaining at the end, or a sneak peak at a new movie. So as to not disappoint you, here’s an relevant item:
Monty-Python-and-the-Holy-Grail-monty-python-and-the-holy-grail-4968359-845-468

The bumpy road to a killer app

As the vehicle known as my brain continues its bumpy ride on the twisting, turning, hilly road to Python knowledge, I see how the vehicle itself can be as challenging to navigate as the road. I have come to the conclusion that learning how to learn is as important as learning itself. At times trying to figure out a coherent learning process is itself even more difficult than the subject I’m trying to learn. Case in point: how to design a coherent, efficient Python application, when I’m still trying to figure out how best to use the parts that make up such an application.

I’m only beginning to understand how to group related code into classes. I want to design my app right from the ground up, yet I don’t believe I know everything I need to know in order to do that. Each chunk of code starts off as an experiment. I start off with an example, say, a Tkinter widget from a tutorial. I copy and paste the example code into my app and tweak it and test it until the widget (for example, a listbox) does (mostly) what I want. I then move on to the next widget. Yes, I know all about unit tests, but how will I know how to write a test for something that I don’t even yet know how it works? If I don’t fully understand how to make my code properly modular, I’m not going to know how the pieces fit into the whole until I go through some trial and error.

Before I know it, my code is piling up into one huge file. It’s ugly, It’s confusing to look at. PEP 8 is weeping and PyCharm is wagging its finger at me reproachfully. But..the code works! I say to myself, “This is is ugly and confusing to look at. There’s a lot of repetitive stuff. I should re-arrange this into classes, or at least put the functions into some kind of order, other than the order in which I was able to successfully Google the way to make them work.” So I cut, copy, paste, re-arrange, and the code looks a lot better, but…I broke stuff. So I think, well forget this mess. I’ve learned so much since I started I can see how this whole thing is wrong from the ground up. Let me start over with a whole new code file. So, I start copying and pasting chunks of code into a new file, but so much of it is the same I wonder why I’m complicating things by creating a whole new file for stuff that’s mostly the same as the old one.

For the better part of two days I nearly tore out (what’s left of) my hair trying to figure out why the function I wrote to update the contents of a list box could no longer find the list box, no matter what order I put my “defs” in. Answer: I’d put the box and the function into a class, but left off the “self.*” in front of them. Now my class has more “selfies” than a pair of lovestruck teenagers on Facebook, but now at least I have the satisfaction of having done the code right, and far fewer squiggly lines and harsh-colored alerts from PyCharm.

By the way, I still like PyCharm. It’s like a pain-in-the-butt accountant at tax time, pestering me to get everything documented, every receipt in order, every pay stub lined up just right, punctuating his admonishments with dire warnings of the consequences of an IRS audit. I complain and grumble at first, yet once the IRS refund check arrives I tell all my friends about my wonderful CPA.

Since I completed DataTools (formerly “CsvChamp”), I have felt bogged down and directionless. Scenarios like the above will come, but to get past them I now believe I need a specific goal to strive for, some kind of “killer app” that will keep me engaged and focused and excited enough to push through the bumpy areas of the road. As I’ve shared before, I’ve wanted to design some kind of data-centric GUI based application. I think it’s time for some fresh inspiration in that direction.

Coolness to the people! [resuming my tinkering with Tkinter]

In my quest to perfect a data-centric GUI app, I have chosen Tkinter as my GUI tool set of choice.  I’ve seen it criticized for being ancient. Heck, according to this, Tkinter was invented in 1994, and the Tk toolkit it came from goes all the way back to 1988! On the other hand, the way I see it, the fact that it’s still used at all is a testimony to its staying power. How many other software tools from 1988 are still around? Ironically, Tkinter is compatible with Python 3, while some competing, newer GUI toolkits such as wxPython are not. I also like Tkinter because it comes with Python — no special downloads or installations required.

Speaking of Python 3, I realize there’s controversy on how to get the Python world fully migrated to Python 3, but it seems there is agreement that Python 3 is the direction we need to move in sooner or later. Therefore, being a newbie, I have decided to go with learning Python 3 for my own coding. I want to save myself the grief of learning how to do everything I ever dreamed of in Python 2, then, down the road, having to convert all my code to Python 3. I’ll still use Python 2 for learning and experimentation, for example, if a course requires it or a library I want to play with doesn’t work with 3.

Okay, now down to business. Here’s my latest progress report on The Adventure..

A while back I was all happy faced and giggly about having conjured up my very own graphical app. Never mind that it didn’t do anything; it was delightful eye candy to me compared to command-line based Python apps. I found that learning Tkinter took a lot of focus. Gone were the days of dragging, dropping and sizing graphical elements on a form like in Microsoft Access. With Tkinter, every box and button must be conjured up with code and programmatically assigned its place in the application window. I found this challenging at first. Just coming up with the pretty interface and making it not look weird required a lot of trial and error. Then I got distracted with my CSV-parsing project.

Now, finally, I am back on track thanks to Real Python’s chapter on simple Tkinter programming, as well as this Tkinter tutorial (note it’s almost TEN YEARS OLD but still works!), along with Tk Docs. I was finally able to get my cool app to perform the cool task of making people cool. Here it is, now in Python 3, with enough coolness to reverse global warming. Of course, unless you have the exact same friends as me, you’ll have to edit the list of cool people as needed.

# Apply Coolness 3.0
# Python 3

from tkinter import *

def coolness():
    """ This function makes people cool.  It will return an
            error if you attempt to use it on someone who is
            already cool."""
    name = txtEntry.get()
    #List of people who are already cool. This is a tuple, meaning
    #these individuals are unchangingly cool.
    cool_people = ("tony",
                   "kevin",
                   "bryan",
                   "matt",
                   "victor",
                   "chris",
                   "ricardo")
    for cool_person in cool_people:
        # If the user inadvertently entered the name of someone
        # who is already cool.
        if cool_person == name.lower():
            result = "ERROR! Invalid usage. \n {} is already cool.".format(name)
            break
        else:
            result = "{} is now a cool person! \n CONGRATULATIONS, {}!"\
                     .format(name,name)
    txtResult.config(text=result)
    txtEntry.config(text="")

# Create the root window
window = Tk()
window.title("Apply Coolness 3.0")

# Create the frame to hold the boxes & button
mainframe = Frame(window)

# We will be using grid() to place widgets in the frame, so..
mainframe.grid()

# Set the variable for the name entered by the user
name = StringVar()

# Label that tells the user what to do.
lblPrompt = Label(mainframe, text="Enter the name of the person to be made cool:")
lblPrompt.grid(column=1, row=1, columnspan=2, padx=3, pady=3)

# Box in which user enters a person's name.
txtEntry = Entry(mainframe)
txtEntry.grid(column=1, row=2, padx=3, pady=3)

# The button that activates the coolness function.
btnCool = Button(mainframe,text="Make Cool!",command=coolness)
btnCool.grid(column=2, row=2, padx=3, pady=3)

lblResult = Label(mainframe, text="Result:")
lblResult.grid(column=1,row=3, sticky=W, padx=3, pady=3)

# Box that displays the result of the procedure.
txtResult = Label(mainframe, width = 20, height = 5, bd="3", relief=SUNKEN)
txtResult.grid(column=1, row=4, columnspan=2, sticky=(E,W))

# Set the focus to the entry box so the user won't have to
# click on it first.
txtEntry.focus()
window.mainloop()

Screenshot:
coolness-rob
Oops:
coolness-tony

DataTools: A Hopefully Useful Toolbox

This set of tools has evolved from CsvChamp and CsvChamp3. Its purpose is to read data from a typical *.csv file, with the first row as column headers, into a Python dictionary. Once in dictionary form, the data can then be formatted for output to a standard text file for viewing and reporting, or uploading to applications (such as payroll processing sites) that require text files with precisely spaced columns of data. The file is loaded into the dictionary using the CsvDict tool. The user can then view the column names (DisplayAllFields), select the desired fields for the output file (SelectFields), and finally, create a nice, neat text file (OutputText). The tools could be modified to display columns of data on a screen or in a GUI element instead of a text file.

This will likely be the final version of my *.csv tinkering. I’m itching to get back to Tkinter and databases which is my ultimate goal. Here is a preview of coming attractions in that regard.

“DataTools” was written in Python 3. Since it’s so rudimentary I didn’t bother version-controlling it or putting it up on Github. So, here it is…enjoy!

# Data Tools 1.0
# by Rob Fowler
# Last Update: 2014-03-05
# Python 3.3.2

# A set of tools for getting and manipulating data from a *.csv file.


import csv
import tkinter
from tkinter import filedialog
import subprocess
import os


# Define exceptions
class DataToolsError(Exception): pass
class WidthError(DataToolsError): pass
class AlignmentSpecError(DataToolsError): pass
class ColumnError(DataToolsError): pass
class FileError(DataToolsError): pass


def GetFile(op):
    """
    Prompts the user to select a file for input or output.
    op = "o" (open a text file) or "s" (save a file).
    Returns a file path for opening or saving.
    """
    root = tkinter.Tk()
    root.withdraw()

    if op.lower() == "o":
        file_path = filedialog.askopenfilename()
        return file_path

    elif op.lower() == "s":
        file_path = filedialog.asksaveasfilename()
        return file_path

    else:
        raise(FileError,"Valid arguments are 'o' (open) or 's' (save).")


def CsvDict(file_path):
    """
    Reads each row of the *.csv file into its own
    dictionary. First row of the *.csv file must be field names. Function adds each row as a sub dictionary, using a unique integer row ID as the key for each row.
    
    Row ID 0 is added as a specification row, providing 
    instructions on how each column is to be output:
        'order': The order in which the column is output.
        'align': How the column is to be aligned within the space
                 allotted to it. 1=left, 2=center, 3=right.
        'display': 1=display column, 0=skip column
        
    Example:
    *.csv file:
    heading1,heading2,heading3,heading4,heading5
    data1,data2,data3,data4,data5
    data6,data7,data8,data9,data10

    Result:
    {0:{'heading1':{'order':0,'align':1,'display':1},
        'heading2:{'order':1,'align':1,'display':1},
        'heading3:{'order':2,'align':1,'display':1},
        'heading4:{'order':3,'align':1,'display':1},
        'heading5:{'order':4,'align':1,'display':1}},
     1:{"heading1":"data1",
        "heading2":"data2",
        "heading3":"data3",
        "heading4":"data4",
        "heading5":"data5"},
     2:{"heading1":"data6",
        "heading2":"data7",
        "heading3":"data8",
        "heading4":"data9",
        heading5":"data10"}}
    """
    delimiter = ','
    quote_character = '"'

    # Read each row into a dictionary.
    file_data = csv.DictReader(open(file_path, "rt"),delimiter=',',
                               quotechar='"')

    field_names = file_data.fieldnames

    # Create the new dictionary that will serve as the data source.
    data_dict = {}

    # Create data spec in 1st row and add column order and display & alignment
    # defaults.
    data_dict[0] = {}
    for field_order in range(0,len(field_names)):
        data_dict[0][field_names[field_order]]={}
        data_dict[0][field_names[field_order]]["order"] = field_order
        data_dict[0][field_names[field_order]]["align"] = 1
        data_dict[0][field_names[field_order]]["display"] = 1

    # Add a unique integer ID as a key for each row, and add the rows as
    # sub dictionaries of data_dict.
    count = 1
    for row in file_data:
        data_dict[count] = row
        count += 1

    # Get number of columns in 1st row of data
    cols = len(data_dict[1])

    for key in data_dict:
        if len(data_dict[key]) != cols:
            raise ColumnError("Data rows don't have same number of columns.")

    for field_name in data_dict[0]:
        width = FieldMaxLen(data_dict, field_name) + 1
        data_dict[0][field_name]["width"] = width

    return data_dict


def ColAlign(text, width, align):
    """
    Returns text aligned within column according to alignment specified.
        text: the text to be aligned
        width: total with of column including blank spaces
        align: how the text is to be aligned within the given 
               column width. 1=left, 2=center, 3=right. 
    """
    # Width must be at least 1 more tha len(text) so there will be at least
    # one space between columns.
    if width <= len(text):
        raise WidthError("Invalid width. Must be > length of text.")

    # Padding = total number of blank spaces
    padding = width - len(text)

    if align == 1: # left
        aligned = text + (padding * " ")

    elif align == 3: # right
        aligned = (padding * " ") + text

    elif align == 2: # centered
    
        # Divide padding by 2 and round down to nearest integer
        spaces_before = int(padding/2)

        # Put the rest of the spaces after the text
        spaces_after = padding-spaces_before

        aligned = (spaces_before * " ") + text + (spaces_after * " ")
    else:

        raise AlignmentSpecError("invalid alignment specification.")

    return aligned


def DisplayAllFields(data_dict):
    """
    Lists fields and their order, width and alignment specifications.
    """
    for display_order in range(0,len(data_dict[0])):
        for field_name in data_dict[0]:
            if data_dict[0][field_name]["order"] == display_order:
                print(str(data_dict[0][field_name]["order"]) + ": " +
                      field_name)


def DisplaySelected(data_dict):
    """
    Lists fields with display set to 1 and their order, width and align specifications.
    """
    for display_order in range(0,len(data_dict[0])):
        for field_name in data_dict[0]:
            if data_dict[0][field_name]["order"] == display_order\
                    and data_dict[0][field_name]["display"] == 1:
                print(str(data_dict[0][field_name]["order"]) + ": " +
                      field_name)


def UnselectAll(data_dict):
    """
    Sets display = 0 on all fields.
    """
    for field_name in data_dict[0]:
        data_dict[0][field_name]["display"] = 0


def SelectAll(data_dict):
    """
    Sets display = 1 on all fields.
    """
    for field_name in data_dict[0]:
        data_dict[0][field_name]["display"] = 1


def SelectFields(data_dict):
    """
    Prompts user to select fields to display, by number.  Use
    DisplayAllFields to obtain field numbers.
    """
    fields_input = input("Enter list of field numbers to display: ")
    field_list = fields_input.split(',')
    fields_int = []
    for num in field_list:
        fields_int.append(int(num))

    UnselectAll(data_dict)

    # Find the field number and set its display flag to 1
    for field_name in data_dict[0]:
        for field_num in fields_int:
            if data_dict[0][field_name]["order"] == field_num:
                data_dict[0][field_name]["display"] = 1


def FieldMaxLen(data_dict, field_name):
    """
    Returns the length of the longest field in a specified column of data
    from a dictionary created by CsvDict.
    """
    max_len = len(field_name)

    # Iterate over each record in dict
    for id in data_dict:

        # Get the length of the current field in the column
        field_len = len(data_dict[id][field_name])

        # If it's greater than the current max length
        if field_len > max_len:

            max_len = field_len

    return max_len


def ResetWidthSpec(data_dict):
    """
    Default column width specification. align=left, width= max text width+1.
    """
    for field_name in data_dict[0]:
        width = FieldMaxLen(data_dict, field_name) + 1
        data_dict[0][field_name]["width"] = width


def OutputText(data_dict, file_path):
    """
    Creates a text file from dict using specs
    """

    with open(file_path, "w") as OutputFile:

        # Write the field names first
        heading = ""

        # len(data_dict[0] = total number of columns
        for display_order in range(0,len(data_dict[0])):
            for field_name in data_dict[0]:
                if data_dict[0][field_name]["order"] == display_order:
                    if data_dict[0][field_name]["display"] == 1:
                        heading += (ColAlign(field_name,
                                    data_dict[0][field_name]['width'],
                                    data_dict[0][field_name]['align']))
        heading += "\n"
        OutputFile.writelines(heading)

        # Now write the actual data.
        # Iterate over each record in data_dict.  Since record 0 is
        # the specs, record 1 is the first data record.
        for record in range(1,len(data_dict)):
            row = ""
            # Count from zero to total number of columns. To make sure the
            # columns are displayed in the correct order, need to compare
            # each column order spec with the count and display it only if
            # it matches
            for display_order in range(0,len(data_dict[0])):

                # Check the specified display order of each field name
                for field_name in data_dict[0]:

                    # If the current field name's order matches the count
                    if data_dict[0][field_name]["order"] == display_order:

                        # If the field's display flag is set
                        if data_dict[0][field_name]["display"] == 1:
                            row += (ColAlign(data_dict[record][field_name],
                                        data_dict[0][field_name]['width'],
                                        data_dict[0][field_name]['align']
                                        ))
            row += "\n"
            OutputFile.writelines(row)
            
    # Open the file.
    # subprocess.call(('xdg-open', file_path)) #Linux
    os.startfile(file_path) # Windows