Make your REPL extendable with your own toolset

The Cmd
class in python is useful for a quick terminal ui. Just your own repl. In this article we will extend the repl with other repls. You can learn some python meta programming to extend your toolset.
First let’s create our “hello world” cmd in the file hw.py
.
# store this in hw.py
from cmd import Cmd
class HelloWorld(Cmd):
"Hello world cmd.Cmd as an example"
def __init__(self):
super().__init__() # call the mother Cmd.__init__
self.name = "world" # give a initial name to get the classic hello world
# every method starting with 'do_' is a repl command.
# the args variable store a string with the arguments
# i.e. you typed "name Albert Einstein" the
# string "Albert Einstein" would be in args.
def do_name(self, args):
"set your name"
self.name = args
print(f"Your name {self.name} is set.", file=self.stdout)
def do_hello(self, args):
"say hello"
print(f"Hello, {self.name}", file=self.stdout)
if __name__ == "__main__":
# if you run "python hw.py" directly, this would be executed
HelloWorld().cmdloop()
This example from my previous article is a good start. I’ve built a lot of tools with this Cmd
class. Ai, todo lists, webscraping, accounting. A lot of times they call apis and other stuff. It’s not that fun to have everything in a mono-repl. Let’s have some fun to build a MasterRepl to load these Cmd classes’s as modules.
To manage other Repls we need only a handful of commands:
load: The core to load a python module with the classic dot notation ‘my.repls.helloworld’
list: List all the modules with the given description
default: A command which is triggered, when no “do_” method matched
Let’s dive into the code:
import importlib
from cmd import Cmd
class MasterRepl(Cmd):
def __init__(self):
super().__init__()
self.modules = {} # Here we will store our other repl's
def do_exit(self, args):
"exit"
return True # when True is returned, the Cmd().cmdloop() will end
def do_load(self, args):
"load another python as a module. `load <variable_name> <module_import>"
try:
# Let's split everything into two strings: the first word and the rest
module_name, module_string = args.split(maxsplit=1)
# import_module would be like "import hw as module_obj"
module_obj = importlib.import_module(module_string)
# dir(..) returns all attributes as a list of strings. All kind of
# variables like __file__, __doc__, ... are also included. so we
# have to find the Cmd. And we assume that in a class there is only
# one repl stored.
for n in dir(module_obj):
# if n is a string of '__doc__' with getattr you get the same
# as module_obj.__doc__
maybe_cmd_subclass = getattr(module_obj, n)
if type(maybe_cmd_subclass) is type and maybe_cmd_subclass != Cmd:
if issubclass(maybe_cmd_subclass, Cmd):
# so we found something. To get an Instance of the repl
# it has to be called with 'repl()'
self.modules[module_name] = maybe_cmd_subclass()
except Exception as e:
print(f"Error on loading the module {args}: {e}")
def do_list(self, args):
"list all the modules with their help text"
try:
for m_name in self.modules.keys():
print(f"--- {m_name} ---")
print(self.modules[m_name].__doc__) # yes some documentation
except Exception as e:
print(f"Error: {e}")
def default(self, args):
try:
module_name, call_string = args.split(maxsplit=1) # same as in load
# onecmd: "Interpret the argument as though it had been
# typed in response to the prompt."
# https://docs.python.org/3/library/cmd.html#cmd.Cmd.onecmd
self.modules[module_name].onecmd(call_string)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
MasterRepl().cmdloop()
That’s it.
Let’s recap some special commands:
dir(obj)
: return a list of all the attributes as stringsgetattribute(obj, name)
: return the attributeissubclass(class_a, class_mother)
: return a bool whenclass_a
is a subclass ofclass_mother
importlib.import_module(module_string)
: return the loaded module
Now let’s test our master class and load the hello world repl:
(Cmd) load hello_world hw
(Cmd) list
--- hello_world ---
Hello world cmd.Cmd as an example
(Cmd) hello_world name Peter
Your name Peter is set.
(Cmd) hello_world hello
Hello, Peter
(Cmd) hello_world help
Documented commands (type help <topic>):
========================================
hello help name
(Cmd) hello_world help name
set your name
Subscribe to my newsletter
Read articles from Adrian directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
