1
\$\begingroup\$

The Project I need a "Look Up Table" for Tags assigned to Accounts. Multiple Tags can be applied to the same Account, but each Tag points to only one Account: EG. Many-to-One lookup table. I discovered an SQLLite database would be sufficient.

As a LUT/DB the database is created once (just use SQLLite Browser for edits). For this reason I decided to skip implementing the rest of CRUD. Once the user starts the mapping, you have to complete the whole process.

Problem

CRUD would have been easier! ... It is easy to script a series of inputs. The difficulty happens when you require each input to have multiple steps:

  • validate user input
  • lookup input in DB
  • user approval

The common Python input idiom uses while True & break, but it quickly gets tricky to break from multiple levels of nested while loops.

Solution

Naively, I wanted a goto() function, but this is verboten in Python. Suggestions to avoid goto() included making functions, and using exceptions. I found one intereseting unusual solution, but was unable to adapt it to my needs. I ended up creating functions and choosing a state-like solution:

import random, re
# Fake tags for loop
til = [{'name' : 'ABC', 'id' : '1'}, {'name' : 'XYZ', 'id' : '2'}, {'name' : 'LMN', 'id' : '3'}]

# TWO HELPER FUNCTIONS
def hf_valid_entry(user_input):
    while True:
        if not user_input: 
            user_input = input("    1. Assign Account#: ")
        if not re.match("\d{4,5}(-\d{2})?", user_input):
            print(f"    2. Bad form: `{user_input}`")
            user_input = None
        else:
            print("    2. Good form.")
            return user_input

def hf_check_acc_num(user_input):
    try:
        # Fake DB Lookup
        record = {'name': 'FOO', 'id' : '1', 'account_number' : '1234'}
    except Exception as e:
        print(e)
        return False
    
    # Simulate bad lookup
    if not random.choice([True, False, False]):
        print("    3. No Record Found.")
        return False
    else:
        print("    3. Found Record.")
        return record

# THE LOOP
for ti in til:
    print("= " * 20)
    print(f"TAG: {ti['name']}") 
    breaker = False
    while True:
        user_input = None
        # VALIDATE ENTRY
        user_input = hf_valid_entry(user_input)          
        print("- " * 20)
        print("Review selection:")
        print(f"TAG NAME: {ti['name']}") 
        # LOOKUP INPUT IN DATABASE  
        record = hf_check_acc_num(user_input)
        if record:
            # PREVIEW
            print(f"Account#: {record['account_number']} - {record['name']}")
            #breaker = False
            response3 = None
            # APPROVE
            while not response3 or response3.lower() not in ('yes', 'y', 'no', 'n'):
                response3 = input("    4. Approve assignment? (y)es, (n)o: ")
                if response3.lower() in ("yes", "y"):
                    breaker = True
                    break
                else:
                    print("5. Not approved!")
                    break
        else:
            pass
            # RETURN TO SAME TAG
        if breaker:
            print("5. Approved!")
            # GO TO NEXT TAG
            break
        

I'm self-taught programmer, and this is a personal project. If you see any redundancies or bad form, I'd appreciate the feedback. I've been making a lot of CLI tools, and so if you have any pseudo-code suggestions for refactoring, that would be great.

Here is the successful test run (enter 1234 to pass validation):

Iterate list of tags:

  1. assign account number
  2. validate account number
  3. Look up account number in 'db'
  4. Approve tag-account number assignment
  5. if not approved, try again (same tag)
    = = = = = = = = = = = = = = = = = = = = 
        1. Assign Account#: 1234
        2. Good form.
    - - - - - - - - - - - - - - - - - - - - 
    Review selection:
    TAG ID: 1
    TAG NAME: ABC
        3. No Record Found.              <-- SIMULATE BAD DB LOOKUP
        1. Assign Account#: 1234         <-- RETRY
        2. Good form.
    - - - - - - - - - - - - - - - - - - - - 
    Review selection:
    TAG ID: 1
    TAG NAME: ABC                        <-- RETRY WITH SAME TAG
        3. No Record Found.
        1. Assign Account#: 1234
        2. Good form.
    - - - - - - - - - - - - - - - - - - - - 
    Review selection:
    TAG ID: 1
    TAG NAME: ABC
        3. Found Record.
    Account#: 1234 - FOO
        4. Approve assignment? (y)es, (n)o: y
    5. Approved!
    = = = = = = = = = = = = = = = = = = = = 
    TAG: XYZ                             <-- NEXT TAG
        1. Assign Account#: 1234
        2. Good form.
    - - - - - - - - - - - - - - - - - - - - 
    Review selection:
    TAG ID: 2
    TAG NAME: XYZ
        3. Found Record.
    Account#: 1234 - FOO
        4. Approve assignment? (y)es, (n)o: n  <-- REJECT
    5. Not approved!
        1. Assign Account#: 1234           
        2. Good form.
    - - - - - - - - - - - - - - - - - - - - 
    Review selection:
    TAG ID: 2
    TAG NAME: XYZ                             <-- SAME TAG  
        3. No Record Found.
        1. Assign Account#: 1234
        2. Good form.
    - - - - - - - - - - - - - - - - - - - - 
    Review selection:
    TAG ID: 2
    TAG NAME: XYZ
        3. Found Record.
    Account#: 1234 - FOO
        4. Approve assignment? (y)es, (n)o: y
    5. Approved!
    = = = = = = = = = = = = = = = = = = = = 
    TAG: LMN                                   <-- MOVE ON TO LAST TAG
\$\endgroup\$
4
  • \$\begingroup\$ Wouldn't it be easier to create a text file with a tag and account number on each line? The script would load and parse the file, validate the account numbers, and then create the LUT in the database. \$\endgroup\$
    – RootTwo
    Commented Dec 15, 2020 at 6:56
  • \$\begingroup\$ This question is not complete. It's missing, at the least, your imports (re, random). Please show your complete code, and if Fake tags for loop can be replaced with more of your "real" code, please do that, too. \$\endgroup\$
    – Reinderien
    Commented Dec 15, 2020 at 19:29
  • 3
    \$\begingroup\$ I took a stab at sanitizing this program but I can't. It's littered with stubs and faked-out database access. You can have your cake and eat it too: use all-real code, and follow it up with unit tests that perform proper mocking so that a real database is not needed. \$\endgroup\$
    – Reinderien
    Commented Dec 15, 2020 at 19:39
  • \$\begingroup\$ "Fake tags for loop" The real tags are in SQLLite, and since this is about Python input(), ti['name'] is as good as ti.name. "unit tests that perform proper mocking so that a real database is not needed" Wow. I don't know how to do that yet. Next week when I have time. \$\endgroup\$
    – xtian
    Commented Dec 17, 2020 at 22:56

0

Browse other questions tagged or ask your own question.