'How to add aliases to an input dictionary?

Recently I started a project. My goal was it to have a script, which, once launched, could be able to control actions on the hosts computer if an instruction was send via email. (I wanted this so I could start tasks which take a long time to complete while I'm away from home)

I started programming and not long after I could send emails, receive emails and analyze their content and take actions responding to the content in the email.

I did this by using an input dictionary, which looked like this:

contents_of_the_email = "!screen\n!wait 5\n!hotkey alt tab"


def wait(sec):
    print(f"I did nothing for {sec} seconds!")


def no_operation():
    print("Nothing")


def screenshot():
    print("I took an image of the screen and send it to your email adress!")


def hotkey(*args):
    print(f"I pressed the keys {', '.join(args)} at the same time")


FUNCTIONS = {
    '':no_operation,
    '!screen': screenshot,
    '!hotkey': hotkey,
    '!wait': wait
}


def call_command(command):
    function, *args = command.split(' ')
    FUNCTIONS[function](*args)


for line in contents_of_the_email.split("\n"):
    call_command(line)

In total I have around 25 functions each with its own response. I replaced the actual code for the commands with simple print statements as they are not needed to understand or replicate my problem.

I then wanted to add aliases for the command, so for example you would be able to type "!ss" instead of "!screen". I did achieve this using another line in the dictionary:

FUNCTIONS = {
    '':no_operation,
    '!screen': screenshot,
    '!ss':screenshot,
    '!hotkey': hotkey,
    '!wait': wait
}

But I didn't like this. It would fill up the whole dictionary if I did it for every alias I am planning to add and it would make my code very messy. Is there any way to define aliases for commands separately and still keep the dictionary clean and simple? I would desire something like this in a separate aliases.txt file:

screen: "!screen", "!ss","!screenshot","!image"
wait: "!wait","!pause","!sleep","!w"
hotkey: "!hk","!tk"

If this is possible in python I would really appreciate to know!



Solution 1:[1]

You can use following solution:

import json
contents_of_the_email = "!screen\n!wait 5\n!hotkey alt tab"


def wait(sec):
    print(f"I did nothing for {sec} seconds!")


def no_operation():
    print("Nothing")


def screenshot():
    print("I took an image of the screen and send it to your email address!")


def hotkey(*args):
    print(f"I pressed the keys {', '.join(args)} at the same time")


# FUNCTIONS DICT FROM JSON
with open("aliases.json") as json_file:
    aliases_json = json.load(json_file)

FUNCTIONS = {}
for func_name, aliases in aliases_json.items():
    FUNCTIONS.update({alias: globals()[func_name] for alias in aliases})


def call_command(command):
    function, *args = command.split(' ')
    FUNCTIONS[function](*args)


for line in contents_of_the_email.split("\n"):
    call_command(line)

aliases.json:

{
  "screenshot": ["!screen", "!ss","!screenshot","!image"],
  "wait": ["!wait","!pause","!sleep","!w"],
  "hotkey": ["!hk","!tk", "!hotkey"]
}

is that what you looking for?

Solution 2:[2]

You can go from a dictionnary of callables and list of shortcuts to a dictionnary of shortcuts to callables fairly easily with for loops.

# long dict of shortcuts to callables
goal = {'A': 0, 'B': 0, 'C': 1}

# condensed dict, not in .txt, but storable in python
condensed = {0: ['A', 'B'], 1: ['C']}

# expand the condensed dict
commands = {}
for func, shortcuts in condensed.items():
    for shortcut in shortcuts:
        commands[shortcut] = func

# or with a comprehension
commands = {s: f for f, ls in condensed.items() for s in ls}

# verify expanded and goal are the same
assert commands == goal

Solution 3:[3]

You could do what you want by first creating a dictionary mapping each alias to one of the functions. This would require parsing the aliases.txt file — which fortunately isn't too difficult. It make use of the ast.literal_eval() function to convert the quoted literal strings in the file into Python strings, as well as the built-in globals() function to look up the associated functions given their name it the file. A KeyError will be raised if there are any references to undefined functions.

Note I changed your aliases.txt file to the following (which makes a little more sense):

screenshot: "!screen", "!ss","!screen","!image"
wait: "!wait","!pause","!sleep","!w"
hotkey: "!hk","!tk"

Below is a runnable example of how to do it:

from ast import literal_eval

ALIASES_FILENAME = 'aliases.txt'

# The functions.
def wait(sec):
    print(f"I did nothing for {sec} seconds!")

def no_operation():
    print("Nothing")

def screenshot():
    print("I took an image of the screen and send it to your email adress!")

def hotkey(*args):
    print(f"I pressed the keys {', '.join(args)} at the same time")

# Create dictionary of aliases from text file.
aliases = {}
with open(ALIASES_FILENAME) as file:
    namespace = globals()
    for line in file:
        cmd, other_names = line.rstrip().split(':')
        aliases[cmd] = namespace[cmd]  # Allows use of actual function name.
        for alias in map(literal_eval, other_names.replace(',', ' ').split()):
            aliases[alias] = namespace[cmd]

def call_command(command):
    function, *args = command.split(' ')
    if function in aliases:
        aliases[function](*args)

# Sample message.
contents_of_the_email = """\
!screen
!wait 5
!hk alt tab
"""

# Execute commands in email.
for line in contents_of_the_email.split("\n"):
    call_command(line)

Output:

I took an image of the screen and send it to your email adress!
I did nothing for 5 seconds!
I pressed the keys alt, tab at the same time

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 pL3b
Solution 2
Solution 3