Python Forum
Dynamically create functions from Json
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Dynamically create functions from Json
#1
Bonjour experts ! Big Grin
It is still for my printer controller project.
I'm now trying to make a lib to get a flexible, and easy tom modify command list.
So the goal for now is that I want to create a custom user command list, with functions declared in a class.

So here is what I've done :

#File name = test.py
from printer import *

ML390 = Printer(1, "commands.json")

ML390.formFeed()
#File name = commands.json
[
  {"name":"lineFeed", "command":"x0A"},
  {"name":"carriageReturn", "command":"x0D"},
  {"name":"formFeed", "command":"x0C"},
  {"name":"setLetterQuality", "command":"x1B x78 x32"},
  {"name":"setUtility", "command":"x1B x78 x31"}
]
#File name = printer.py
import platform #Lib to check os
import json

class Printer(): #Main class
    def __init__(self, port, file): #"port" is the parralel port where the printer is pluged in, "file" is the json file containg the custom functions and command to send to the printer
        self.os = platform.system() #Returning os in a variable
        self.data = json.load(open(file)) #Opening user selected file

        if type(port) != int: #Checks if the port is an int
            raise Exception("PORT ERROR : Port must be an intreger") #Returns error
        if port <= 0: #Checks if the port is the right one
            raise Exception("PORT ERROR : Port must be 1 or higher") #returns error

        if self.os == "Linux" : #If on Linux
            self.port = open("/dev/lp"+str(port-1), "w") #Open as /dev/lpx
        elif self.os == "Windows" : #If on Winows
            self.port = open("LPT"+str(port), "w") #Open as LPTx
        else: #If on oter system
            raise Exception("OS ERROR : Unsupported OS") #No supported os error

        for n in range(len(self.data)): #Cor all the commands defined in the json
            exec(f"""def {self.data[n]["name"]}(self):
                self.printText(data[n]["command"])""") #Create a custom function from the json, function name is name in the json

    def printText(self, text):
        text = str(text)
        self.port.write(text)
        self.port.flush()
When running test.py, I get this error :
Error:
Traceback (most recent call last): File "test.py", line 6, in <module> ML390.formFeed() AttributeError: 'Printer' object has no attribute 'formFeed'
I don't know how to declare the functions in the main class, but keep the dynamic function creation when calling the class (__init__). Huh Cry.

As I would say in my country : "Je suis chiffoné !"

Help me please.
Thank you in advance,
Clément.
Reply
#2
You're defining functions that are local to the __init__ method, so they won't be accessible to the class as a whole. That can be fixed, but I'm not sure it should be. Dynamically generating code is pretty sketchy, and you should be very suspicious whenever you see the words "exec" or "eval" in source.

For starters, how are you planning on calling these dynamic functions?
What if one of the functions is "printText"? Would all the others stop working, because they're trying to use a version of printText which no longer exists?

I think it'd make more sense for you to load them as a dict, then have a single method that looks them up. So instead of ML390.formFeed(), you'd do ML390.dispatch("formFeed"). Then that function would look something like:
def dispatch(self, key):
    if key in self.data:
        return self.printText(self.data[key])
    raise Exception(f"Invalid key: {key}")
Reply
#3
the best way to create a command driven execution is to use a dictionary, here's a simple example:
class SoftwareCommands:
    def __init__(self):
        self.commands = {
            'func_a': self.func_a,
            'func_b': self.func_b,
            'willy nilly': self.willy_nilly
        }
    
    def func_a(self):
        print("\nHi, I'm func_a")
    
    def func_b(self):
        print("\nHi, I'm func_b")
    
    def willy_nilly(self):
        n = 0
        while n < 10:
            print('n: {}'.format(n))
            n += 1


def main():
    command = None
    sc = SoftwareCommands()
    while command != 'quit':
        command = input("Enter a command, quit to end: ").strip().lower()
        if command in sc.commands:
            sc.commands[command]()

if __name__ == '__main__':
    main()
outout:
Output:
Enter a command, quit to end: func_a Hi, I'm func_a Enter a command, quit to end: willy nilly n: 0 n: 1 n: 2 n: 3 n: 4 n: 5 n: 6 n: 7 n: 8 n: 9 Enter a command, quit to end: quit
Reply
#4
Hi nilamo and Larz60+,
Thank you for your help !

So nilmao, you're totally right. I don't know why I haven't done that before, I'm stupid. So i worked on it.
Larz60+, I saw your code, that's not exactly what I was looking for, but nilmao found what I was looking for. Anyway, I keep it in mind, it could be useful to another project.

Anyway, following your idea nilmao, here is what I ended up with :
#NAME = test.py
from printer import *

ML390 = Printer(1, "commands.json")

ML390.command("setUtility")
#NAME = commands.py
{
  "lineFeed":{"command":"0A", "text":"Line Feed"},
  "carriageReturn":{"command":"0D", "text":"Carriage Return"},
  "formFeed":{"command":"0C", "text":"formFeed"},
  "setLetterQuality":{"command":"1B 78 32", "text":"Set Letter Quality"},
  "setUtility":{"command":"1B 78 31", "text":"Set Utility"}
}
#NAME = printer.py
import platform #Lib to check os
import json

class Printer(): #Main class
    def __init__(self, port, file): #"port" is the parallel port where the printer is plugged in, "file" is the json file containing the custom functions and command to send to the printer
        self.os = platform.system() #Returning os in a variable
        self.data = json.load(open(file)) #Opening user selected file

        if type(port) != int or port <= 0: #Checks if the port is an int
            raise Exception(f"Invalid port : {port}") #Returns error

        if self.os == "Linux" : #If on Linux
            self.port = open("/dev/lp"+str(port-1), "w") #Open as /dev/lpx
        elif self.os == "Windows" : #If on Winows
            self.port = open("LPT"+str(port), "w") #Open as LPTx
        else: #If on oter system
            raise Exception(f"Invalid OS : {self.os}") #No supported os error

    def printText(self, text):
        self.port.write(str(text))
        self.port.flush()

    def command(self, key): #custom command generator
        if key in self.data: #If the key is in the json
            commands = self.data[key]["command"] #Extract the command string from the json
            print(commands) #TEMPORARY

            commands = commands.split(" ") #Split each hex number individually
            for n in range(len(commands)): #For each hex number
                commands[n] = f"\x{commands[n]}" #add "\x" before
            commands = "".join(commands) #Put everything back into a single string

            self.printText(commands) #Send the command to the printer

            print(self.data[key]["text"]) #TEMPORARY
            print(commands) #TEMPORARY
        else: #If the key isn't in the json
            raise Exception(f"Invalid key: {key}") #Wrong key error
The new problem now is that in line 31, (you already know what will happen Wink), python tells me that I have to put the hex value after. And if I replace the string by "\\x" or r"\x", when running the command, I the printer litearally prints "\x0A" or "\x0B", or ... (not the quotes).
Should I create an other thread or can you help me now ?

Thanks again (I never say it enough Big Grin),
Clément.
Reply
#5
Does this help?
>>> code = "1B"
>>> as_int = int(code, base=16)
>>> as_int
27
>>> as_hex = hex(as_int)
>>> as_hex
'0x1b'
Reply
#6
Hi nilamo,
I'm so happy today, you can't believe.
Everything is fixed !
Thank you !
When reading your message, this did remind me a previous message of Larz60+ in this thread https://python-forum.io/Thread-Control-a-dot-matrix-printer. And was showing the use of chr().

So I converted all my hex values into decimal values in the json.
And I changed a bit the lib to use chr()...
And that's all !
Here is the result
#NAME = commands.json
{
  "lineFeed":{"command":"10", "text":"Line Feed"},
  "carriageReturn":{"command":"13", "text":"Carriage Return"},
  "formFeed":{"command":"12", "text":"Form Feed"},
  "newLine":{"command":"10 13", "text":"New Line"},

  "setLetterQuality":{"command":"27 120 49", "text":"Set Letter Quality"},
  "setUtility":{"command":"27 120 48", "text":"Set Utility"},

  "fontRoman":{"command":"27 107 48"},
  "fontHelvette":{"command":"27 107 49"},
  "fontCourrier":{"command":"27 107 50"},
  "fontPrestige":{"command":"27 107 51"},
  "fontGothic":{"command":"27 107 54"},
  "fontBold":{"command":"27 107 55"},

  "styleNormal":{"command":"27 113 0"},
  "styleOutline":{"command":"27 113 1"},
  "styleShadow":{"command":"27 113 2"},
  "styleOutlineShadow":{"command":"27 113 3"},

  "10Cpi":{"command":"27 80 18"},
  "12Cpi":{"command":"27 77 18"},
  "15Cpi":{"command":"27 103 18"},
  "17Cpi":{"command":"27 80 15"},
  "20Cpi":{"command":"27 77 15"},

  "propOn":{"command":"27 112 1"},
  "propOff":{"command":"27 112 0"},
  "doubleOn":{"command":"27 119 49 27 87 49"},
  "doubleOff":{"command":"27 119 48 27 87 48"}
}
#NAME = printer.py
import platform #Lib to check os
import json

class Printer(): #Main class
    def __init__(self, port, file): #"port" is the parralel port where the printer is pluged in, "file" is the json file containg the custom functions and command to send to the printer
        self.os = platform.system() #Returning os in a variable
        self.data = json.load(open(file)) #Opening user selected file

        if type(port) != int or port <= 0: #Checks if the port is an int
            raise Exception(f"Invalid port : {port}") #Returns error

        if self.os == "Linux" : #If on Linux
            self.port = open("/dev/lp"+str(port-1), "w") #Open as /dev/lpx
        elif self.os == "Windows" : #If on Winows
            self.port = open("LPT"+str(port), "w") #Open as LPTx
        else: #If on oter system
            raise Exception(f"Invalid OS : {self.os}") #No supported os error

    def printText(self, text):
        self.port.write(str(text))
        self.port.flush()

    def command(self, key): #custom command generator
        if key in self.data: #If the key is in the json
            commands = self.data[key]["command"] #Extract the command string from the json

            commands = commands.split(" ") #Split each dec number individually
            for n in range(len(commands)): #For each dec number
                self.printText(chr(int(commands[n]))) #Send the command to the printer
        else: #If the key isn't in the json
            raise Exception(f"Invalid key: {key}") #Wrong key error

    def sendAscii(decimals):
        decimals = decimals.split(" ")
        for n in range(len(decimals)):
            printText(chr(decimals[n]))
#NAME = test.py
from printer import *

ML390 = Printer(1, "commands.json")

ML390.command("cleanPaperBin")

ML390.command("setLetterQuality")
ML390.command("10Cpi")
ML390.command("doubleOn")
ML390.command("underlineOn")
ML390.command("boldOn")
ML390.command("styleOutlineShadow")
ML390.command("fontGothic")
ML390.command("alignCenter")


ML390.command("newLine")
ML390.printText("****************************************\n")
ML390.printText("Thank you nilamo and Larz60+ !\n\n")
ML390.printText("You solved all my problems...\n\n")
ML390.command("newLine")
ML390.command("newLine")
ML390.command("alignLeft")
ML390.command("20Cpi")
ML390.printText("...at least for now ;-p...\n\n")
ML390.command("10Cpi")
ML390.command("alignCenter")
ML390.printText("****************************************\n\n")
ML390.command("formFeed")

ML390.command("reset")
[Image: printer1.jpg]
[Image: printer2.jpg]

I'm so glad it works !
Now the GUI is left to do !

Thanks again,
Clément.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Create variable and list dynamically quest_ 12 4,285 Jan-26-2021, 07:14 PM
Last Post: quest_
  Combine Two Recursive Functions To Create One Recursive Selection Sort Function Jeremy7 12 7,188 Jan-17-2021, 03:02 AM
Last Post: Jeremy7
  JSON Decode error when using API to create dataframe Rubstiano7 4 2,878 Jan-11-2021, 07:52 PM
Last Post: buran
  How to create a basic grid with functions trousers1 2 1,816 Nov-22-2019, 04:16 PM
Last Post: ThomasL
  Create a new list dynamically floatingshed 6 14,144 Nov-20-2017, 01:25 PM
Last Post: floatingshed
  dynamically create a list wfsteadman 0 2,334 Aug-30-2017, 05:34 PM
Last Post: wfsteadman

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020