Using exceptions to control flow and communicate with two separate frontends
I am trying to write a backend for use with a completely text based interface for one shot operations (eg. python scriptname arg executes that argument and exits) and a UI using the curses library for some more detailed editing. My concern is how to properly communicate with both of these. I realized exceptions are probably great for this, but raising exceptions even for positive outcomes feels not okay. It may be fine, but it just feels a bit wrong. The alternative I imagine is to have a separate function call the correct response for each UI, but this feels more complicated than simply using exceptions, even if using them the wrong way. Alternatively, constantly checking what mode the user is in. An example: def remove_by_id(id, table): for task in table[TBL_CONTENTS]: if task.get_id() == id: table[TBL_CONTENTS].remove(task) write_tasks() return raise TaskNotFound(f"No task with id: '{id}'") Before, I would simply have printed the message at the final line of this block, but with separate UIs I need to handle it differently in each caller (curses module or cli module). Example from the cli module: try: remove(options, table) except TaskNotFound as e: print(e.msg) This is absolutely fine and kind of nice, but what if I want to communicate something more complicated? For example: def remove_by_title(title, table): ... if len(matches) > 1: print("Several tasks match provdided title. Select one to remove by index:" ) display_tasks(matches, detailed=True) print(f"Select index in range 1-{len(matches)}.") i = int(input("> ")) i -= 1 else: i = 0 table[TBL_CONTENTS].remove(matches[i]) I can't simply raise an exception as the actual removal would not be taking place, also this behaviours is unique to the cli mode. I would either need to pass the caller as in remove_by_title(title, table, parent), write separate functions or use an object to keep track of the current mode. Either way, some frontend logic would be ending up in my backend logic. This is absolutely solvable with any of these solutions but there must be a more conventional way to do this, and I kind of like the exceptions approach so I would like to keep using that as well instead of constantly checking for mode in the following manner: if caller == CLI: ... elif caller == CURSES: ... Basically I need a good way to separate logic completely. I don't want this type of logic that feels like the backend should not have to care about. I would just like to say "I need more information" in which case the appropriate frontend takes over and provides it, and then the function continues. I found the following post Is it a good practice to use try-except-else in Python? and this quote from the accepted answer: it is common to have top level user-interface code calling code for business logic which in turn calls low-level routines. Situations arising in the low-level routines (such as duplicate records for unique keys in database accesses) can only be handled in top-level code (such as asking the user for a new key that doesn't conflict with existing keys). The use of exceptions for this kind of control-flow allows the mid-level routines to completely ignore the issue and be nicely decoupled from that aspect of flow-control. This details a similar situation, but I don't really understand how I am supposed to implement something like it without halting the functions execution when an exception is raised.

I am trying to write a backend for use with a completely text based interface for one shot operations (eg. python scriptname arg
executes that argument and exits) and a UI using the curses library for some more detailed editing.
My concern is how to properly communicate with both of these. I realized exceptions are probably great for this, but raising exceptions even for positive outcomes feels not okay. It may be fine, but it just feels a bit wrong.
The alternative I imagine is to have a separate function call the correct response for each UI, but this feels more complicated than simply using exceptions, even if using them the wrong way. Alternatively, constantly checking what mode the user is in.
An example:
def remove_by_id(id, table):
for task in table[TBL_CONTENTS]:
if task.get_id() == id:
table[TBL_CONTENTS].remove(task)
write_tasks()
return
raise TaskNotFound(f"No task with id: '{id}'")
Before, I would simply have printed the message at the final line of this block, but with separate UIs I need to handle it differently in each caller (curses module or cli module). Example from the cli module:
try:
remove(options, table)
except TaskNotFound as e:
print(e.msg)
This is absolutely fine and kind of nice, but what if I want to communicate something more complicated? For example:
def remove_by_title(title, table):
...
if len(matches) > 1:
print("Several tasks match provdided title. Select one to remove by index:" )
display_tasks(matches, detailed=True)
print(f"Select index in range 1-{len(matches)}.")
i = int(input("> "))
i -= 1
else:
i = 0
table[TBL_CONTENTS].remove(matches[i])
I can't simply raise an exception as the actual removal would not be taking place, also this behaviours is unique to the cli mode. I would either need to pass the caller as in remove_by_title(title, table, parent)
, write separate functions or use an object to keep track of the current mode. Either way, some frontend logic would be ending up in my backend logic.
This is absolutely solvable with any of these solutions but there must be a more conventional way to do this, and I kind of like the exceptions approach so I would like to keep using that as well instead of constantly checking for mode in the following manner:
if caller == CLI:
...
elif caller == CURSES:
...
Basically I need a good way to separate logic completely. I don't want this type of logic that feels like the backend should not have to care about. I would just like to say "I need more information" in which case the appropriate frontend takes over and provides it, and then the function continues.
I found the following post Is it a good practice to use try-except-else in Python? and this quote from the accepted answer:
it is common to have top level user-interface code calling code for business logic which in turn calls low-level routines. Situations arising in the low-level routines (such as duplicate records for unique keys in database accesses) can only be handled in top-level code (such as asking the user for a new key that doesn't conflict with existing keys). The use of exceptions for this kind of control-flow allows the mid-level routines to completely ignore the issue and be nicely decoupled from that aspect of flow-control.
This details a similar situation, but I don't really understand how I am supposed to implement something like it without halting the functions execution when an exception is raised.