Cooked Input Tutorial, Part Two¶
Introduction:¶
This is the second part of the tutorial for cooked_input
. It assumes that you have completed the first
part of the tutorial. In this tutorial you
will create a simple, menu driven, application that demonstrates some of cooked_input’s
advanced features: tables, menus, and commands.
To start we will import cooked_input and create lists to hold event types and events
from collections import namedtuple
import cooked_input as ci
EventType = namedtuple('EventType', 'id name desc')
Event = namedtuple('Event', 'id date desc type')
event_types = [
EventType(1, 'birthday' ,'a birthday event'),
EventType(2, 'anniversary' ,'an anniversary event'),
EventType(3, 'meeting', 'a meeting event')
]
events = []
Commands:¶
Next we will create some commands for the application. A cooked_input command is a string the user can enter during a cooked_input input request that calls a callback function.
Commands are specified by by passing a dictionary, where the key is the string and the value is
a GetInputCommand
instance, to the commands
parameter of a cooked_input input call.
GetInputCommand
objects consist of a callback function and an optional dictionary of values to be sent to the
callback (cmd_dict
). Command callbacks take three input parameters (cmd_str
, cmd_vars
, and cmd_dict
)
and return a CommandResponse
tuple. cmd_str
and cmd_vars
are the command and any parameters after the
command, and cmd_dict
is the dictionary specified in the GetInputCommand
. CommandResponse
is a tuple where the first value is the return type of the command and the second value the value returned by the
command.
For example, the following code creates a /in_to_mm
command to convert from inches to millimeters
and supplies the result as the input:
def in_to_mm_cmd_action(cmd_str, cmd_vars, cmd_dict):
mm = int(cmd_vars) * 25.4 # 1 inch is 25.4 millimeters
return ci.CommandResponse(ci.COMMAND_ACTION_USE_VALUE, str(mm))
cmds = {'/in_to_mm': ci.GetInputCommand(in_to_mm_cmd_action)}
v = ci.get_float(prompt="How long is it (in mm)", commands=cmds)
print(v)
Running the code, we can use the command to use 2 inches as the input:
>>> How long is it (in mm): /in_to_mm 2
>>> 50.8
In addition to commands that return values, there are CommandResponses
for purely informational
commands (COMMAND_ACTION_NOP) and cancellation commands (COMMAND_ACTION_CANCEL).
Let’s go ahead and add two commands to the event manager application. The first displays a help message help, and the second to cancel the current operation:
def help_cmd_action(cmd_str, cmd_vars, cmd_dict):
help_str = """
Commands:
/?, /help Display this help message
/cancel Cancel the current operation
"""
print(help_str)
return ci.CommandResponse(ci.COMMAND_ACTION_NOP, None)
def cancel_cmd_action(cmd_str, cmd_vars, cmd_dict):
if ci.get_yes_no(prompt='Are you sure?', default='no') == 'yes':
print('\nCommand cancelled...')
return ci.CommandResponse(ci.COMMAND_ACTION_CANCEL, None)
else:
return ci.CommandResponse(ci.COMMAND_ACTION_NOP, None)
help_cmd = ci.GetInputCommand(help_cmd_action)
cancel_cmd = ci.GetInputCommand(cancel_cmd_action)
commands_std = { '/?': help_cmd, '/h': help_cmd, '/cancel': cancel_cmd }
Note
As was done in the help command, you can have more than one command point to the same command GetInputCommand
.
Also, as was done in the cancel commands, you can call cooked_input
functions within a command callback.
More detail about cooked_input commands can be found at: command
Tables:¶
cooked_input can also display tables of data. The easiest way to create a table is to use the create_table()
convenience function. create_table
can create a table any iterable where the fields in each item can be fetched
by field or attribute name, including: objects, dictionaries, and namedtuples.
create_table
requires two parameters: items
, an iterable containing row data for the table, and fields
, the
list of which fields/attributes from the items to show as columns in the table.
Once created, the table can be displayed using its show_table
method:
flds = ['name', 'desc']
tbl = ci.create_table(items=event_types, fields=flds)
tbl.show_table()
will display:
+-------------+----------------------+
| name | desc |
+-------------+----------------------+
| birthday | a birthday event |
| anniversary | an anniversary event |
| meeting | a meeting event |
+-------------+----------------------+
There are several other common parameters for create_table
. field_names
is a list of strings to use for the
column headers, title sets a title for the table, and style
specified a TableStyle
for used when
displaying the table.
You can also use tables for input by either calling the the get_table_input()
convenience function, or the
table’s get_table_choice
method. You can see this in action by trying the following:
>>> v = ci.get_table_input(tbl, prompt='event type')
>>>
>>> +-------------+----------------------+
>>> | name | desc |
>>> +-------------+----------------------+
>>> | birthday | a birthday event |
>>> | anniversary | an anniversary event |
>>> | meeting | a meeting event |
>>> +-------------+----------------------+
>>>
>>> event type: m
>>> >>> print(v)
>>> TableItem(col_values=['a meeting event'], tag=meeting, action=default, item_data=None, hidden=False, enabled=True)
Note
Notice that we only had to type in ‘m’ to choose meeting
, get_table_choice
automatically adds
a ChoiceCleaner as a convenience!
Each row in a cooked_input table is a TableItem
, and by default get_table_choice
will return the
TableItem for the choice entered. This can be modified by setting the default_action
parameter, For instance,
setting default_action
to TABLE_RETURN_FIRST_VAL would have returned “meeting” in the example above.
Note
create_table
can create a table by passing in the query from an ORM-managed database such
as SQLAlchemy. When doing so it’s useful to set the add_item_to_item_data
parameter in create_table
to True. This will automatically attach the full data
for the item to the row’s item_data. In the example above
the id
for the event_type is not in the table. By setting add_item_to_item_data
= True it
could be accessed by through v.item_data['item']['id']
. This makes it easy to get the primary key
of the choice’s database entry even though it’s not shown in the table.
The default action for the table can also be set to a function or callable. The action callback receives
two input parameters. Row
contains the TableItem
for the row chosen, which includes a
values
field containing the columns values for the row. The callback function also receives
action_data
which is an optional dictionary of values to send to the action. For instance,
we can send a capitalized version of the event type with the following default action callback
def cap_action(row, action_item):
val = row.tag.upper()
return val
tbl = ci.create_table(event_types, flds, default_action=cap_action)
would produce:
>>> +-------------+----------------------+
>>> | name | desc |
>>> +-------------+----------------------+
>>> | birthday | a birthday event |
>>> | anniversary | an anniversary event |
>>> | meeting | a meeting event |
>>> +-------------+----------------------+
>>>
>>> event type: a
>>> ANNIVERSARY
Lets create some action callbacks to out application to: add an event, list all of the events and delete all of the events:
def reset_db_action(row, action_item):
cmds = action_dict['commands']
if ci.get_yes_no(prompt='Delete all events? ', default='no',
commands=cmds) == 'yes':
action_dict['events'] = []
def add_event_action(row, action_item):
events = action_dict['events']
event_types = action_dict['event_types']
cmds = action_dict['commands']
desc = ci.get_string(prompt="Event description? ", commands=cmds)
tbl = ci.create_table(event_types, ["name", "desc"], ["Name", "Desc"],
add_item_to_item_data=True)
event_type = tbl.get_table_choice(prompt='Type? ', commands=cmds'])
date = ci.get_date(prompt='Date? ', default='today', commands=cmds)
type_id = event_type.item_data['item'].id
events.append(Event(len(events)+1, date, desc, type_id))
def list_event_action(row, action_item):
events = action_dict['events']
event_types = action_dict['event_types']
if len(events) == 0:
print('\nno events\n')
return
et_dict = {item.id: item.name for item in event_types}
items = []
for e in events:
date = e.date.strftime('%x')
etype = et_dict[e.type]
items.append({'id': e.id, 'date': date, 'desc': e.desc, 'type': etype})
fields = ['date', 'desc', 'type']
field_names = ['Date', 'Desc', 'Type']
tbl = ci.create_table(items, fields, field_names, title='Events')
print('\n')
tbl.show_table()
print('\n')
Note
These action callbacks depend on receiving the commands, events and event_types lists in action_dict
.
Action_dict provides a method of sending data to and from the callback without using global variables. This
mechanism is useful to provide context such as: database sessions/connections, user information, etc.
Running the application:¶
Try running the application (the full code is available at events.py). You can test adding events and listing them. Don’t forget to try the commands. For example, start adding event and type \cancel to stop in the middle of the operation without adding the event.
You may also want to try adding a new menu item to the database menu to add an event type. You’ll see just how easy it is to add new functionality to an application with cooked_input.
From Here:¶
That completes the second part of the cooked_input tutorial. For more information take a look at the how-to/FAQ section of the documentation. You can also look at the various examples.