blog.humaneguitarist.org
easy command line and RESTful interfaces to Python functions with a Plac intensifier
[Mon, 03 Sep 2018 17:04:24 +0000]
For work, I've been using the excellent Plac [https://pypi.org/project/plac/] module for Python to make it easy to add command line interfaces to scripts. Because of our desire to have a web interface to our scripts, too, I started thinking about how to make it easy to do that with the same ease that Plac provides command line interfaces. But first, take this simple code example to see Plac in action:
#!/usr/bin/python 3
# hello_you.py
import plac
def main(name: ("your first name"),
exclaim: ("use an exclamation mark", "flag", "x")=False,):
"""Prints/Returns "Hello @name."\
\nEx: `python3 hello_you.py Nitin -x`"""
punc = "." if not exclaim else "!"
greet = "Hello {}{}".format(name, punc)
print(greet)
return greet
if __name__ == "__main__":
plac.call(main)
Now, here are some usage examples:
> py -3 .\hello_you.py -h
usage: hello_you.py [-h] [-x] name
Prints/Returns "Hello @name."
Ex: `python3 hello_you.py Nitin -x`
positional arguments:
name your first name
optional arguments:
-h, --help show this help message and exit
-x, --exclaim use an exclamation mark
> py -3 .\hello_you.py Nitin
Hello Nitin.
> py -3 .\hello_you.py Nitin -x
Hello Nitin!
Now, here's the same script but using a wrapper around Plac that I'm calling Placissimo.
#!/usr/bin/python 3
# hello_you.py
import placissimo
def main(name: ("your first name"),
exclaim: ("use an exclamation mark", "flag", "x")=False,):
"""Prints/Returns "Hello @name."\
\nEx: `python3 hello_you.py Nitin -x`"""
punc = "." if not exclaim else "!"
greet = "Hello {}{}".format(name, punc)
print(greet)
return greet
if __name__ == "__main__":
placissimo.call(main)
The only differences are the import statement and the
.call
function's namespace. With this wrapper, the usage examples above will work exactly the same. But there's one difference. It lets me do this:
> py -3 .\hello_you.py servissimo -h
usage: hello_you.py [-h] server [host] [port]
Creates an HTTP server at @port.
positional arguments:
server trigger to launch a server
host host to use
port port number to use
optional arguments:
-h, --help show this help message and exit
As you can see the
servissimo
command changes the behavior. Instead of using the command line version, I can launch a web server with a default host of
localhost:8080
. Let me do that:
py -3 .\hello_you.py servissimo
<Flask 'hello_you'>
2018-09-03 12:32:58,767 - werkzeug - INFO - * Running on http://localhost:8080/ (Press CTRL+C to quit)
Now, I can go to
localhost:8080
in my browser and I'll see a JSON response:
{
"code": 1,
"input": {},
"output": "TypeError(\"main() missing 1 required positional argument: 'name'\",)"
}
Let me pass in the required argument a la
localhost:8080/?name=Nitin
:
{
"code": 0,
"input": {
"name": "Nitin"
},
"output": "Hello Nitin."
}
I'll also try
localhost:8080/?name=Nitin&exclaim=True
:
{
"code": 0,
"input": {
"name": "Nitin",
"exclaim": "True"
},
"output": "Hello Nitin!"
}
Note that I had to use the long hand argument
exclaim
instead of the short hand
x
as I did in the command line. I also had to explicitly use
=True
. Big deal. For now, I'm only supporting GET arguments, but supporting POST as well won't be hard. I also have it using the inspect [https://docs.python.org/3/library/inspect.html] module to support some online help at
localhost:8080/info
:
{
"module": {
"name": "hello_you"
},
"function": {
"name": "main",
"help": "Prints/Returns \"Hello @name.\" \nEx: `python3 hello_you.py Nitin -x`",
"args": {
"name": "your first name",
"exclaim": [
"use an exclamation mark",
"flag",
"x"
]
}
}
}
I'm also going to have this add handlers to the root logger. That way, if this is being used to provide command line or web access to a function that uses logging, I can route all logging to a file, etc. I also want to add an option to launch a websocket server that will log as JSON and emit the tail of the JSON log. This will allow someone to connect to the socket server in their HTML/JavaScript page and show logging in near real-time. Anyway, I think this is something I'm going to really flesh out. I think it'll be a useful and easy way to simultaneously create command line and RESTful interfaces to a Python script.