Package for creating a CLI from classes
Project description
A flexible Python package for building command-line interfaces with object hierarchies, auto-completion, and dynamic parameter validation.
Features
Object-oriented command structure
Nested command hierarchies using subparsers
Type hints and automatic type conversion
Dynamic command completion
Parameter validation and auto-completion
Caching for improved performance
Expression evaluation support
Installation
pip install cliify
Quick Start
Here’s a simple example showing the command parsing:
from cliify import command, commandParser
@commandParser
class Calculator:
def getValidOperations(self):
return ['+', '-', '*', '/']
@command(completions={'operation': lambda self: self.getValidOperations()})
def calculate(self, a: int, operation: str, b: int):
"""
Calculate the result of an operation
Args:
a: First operand
operation: Operation to perform
b: Second operand
"""
if operation == '+':
return a + b
elif operation == '-':
return a - b
elif operation == '*':
return a * b
elif operation == '/':
return a / b
calc = Calculator()
#can use positional or named arguments
result = calc.parseCommand("calculate 5 + 3") # Returns 8
result = calc.parseCommand("calculate a: 5, operation: + , b: 3") # Returns 8
help = calc.getHelp("calculate") # Return help message from docstring
Nested Commands
You can create hierarchical command structures with subparserss (supports single object or a dictionary of objects):
@commandParser()
class Device:
def __init__(self, name):
self.name = name
self.value = 0
@command(completions={'value': [0, 1, 2, 3, 4, 5]})
def setValue(self, value: int):
self.value = value
@commandParser(subparsers=['devices'])
class Controller:
def __init__(self):
self.devices = {}
self.singleDevice = Device("singleDevice")
@command(help="Add a new device") #help message can also be explicitly set
def addDevice(self, name: str):
self.devices[name] = Device(name)
# Devices can have their own commands
class DeviceCommands:
@command(help="Set device value", completions={'value': [0, 1, 2, 3, 4, 5]})
def setValue(self, value: int):
self.value = value
controller = Controller()
result = controller.parseCommand("addDevice device1")
result = controller.parseCommand("device1.setValue 3")
result = controller.parseCommand("singleDevice.setValue 3")
Dynamic Completions
The package supports various ways to define completions:
Static Lists:
class myController:
self.mode = None
self.min_value = 0
self.max_value = 10
#static list of values
@command(completions={'mode': ['auto', 'manual', 'hybrid']})
def setMode(self, mode: str):
self.mode = mode
def getAvailablePorts(self):
return ['COM1', 'COM2', 'COM3']
#method reference
@command(completions={'port': 'getAvailablePorts'})
def connect(self, port: str):
self.port = port
#lambda function
@command(completions={'value': lambda self: range(self.min_value, self.max_value + 1)})
def setValue(self, value: int):
self.value = value
controller = myController()
completions = controller.getCompletions("setMode ") # Returns ['mode']
completions = controller.getCompletions("setMode mode: ") # Returns ['auto', 'manual', 'hybrid']
Caching and Performance
The completion tree can be cached for better performance:
controller = Controller()
# First call builds the tree
completions = controller.getCompletions("set", use_cache=True)
# Subsequent calls use cached tree
completions = controller.getCompletions("get", use_cache=True)
Use the @invalidatesTree decorator for methods that modify the command structure:
@invalidatesTree
def addCommand(self, name: str, command: Callable):
self.commands[name] = command
Type Conversion
The parser automatically converts string inputs to the correct Python types based on type hints:
@command(help="Configure sensor")
def configureSensor(self,
id: int, # Converts to integer
name: str, # Handles quoted strings
active: bool, # Converts to boolean
gains: List[float] # Converts to list of floats
):
pass
Bytes handling
bytes type arguments can handle multiple methods of input:
@command(help="Send data")
def sendData(self, data: bytes):
pass
# Hexadecimal string
result = controller.parseCommand("sendData 0xdeadbeef")
result = controller.parseCommand("sendData 0x00 0x01 0x02")
# Base64 encoded string
result = controller.parseCommand("sendData ZGVhZGJlZWY=")
# Raw bytes
result = controller.parseCommand("sendData b'hello world'")
Expression Evaluation
Enable expression evaluation for dynamic values:
@commandParser(allow_eval=True)
class Calculator:
@command(help="Calculate result")
def calculate(self, value: int):
return value
calc = Calculator()
result = calc.parseCommand("calculate $(2 * 3)") # Evaluates expression
Out-of-the-Box UI
This package contains some out of the box support for a command line interface using prompt_toolkit. The CommandCompleter class can be used with prompt_toolkit to provide a command line interface with auto-completion and history. There are also ready-to-use UI classes for a simple command line interface and a more advanced command line interface with a command history.
The below example will create a split console app (a console with a command line interface on the bottom and a log on the top) with auto-completion and history. By default logs and print statements will be redirected to the log console.
from cliify import command, commandParser
from cliify.ui.prompt_toolkit import SplitConsole
@commandParser()
class Device:
def __init__(self, name):
self.name = name
self.value = 0
@command(completions={'value': [0, 1, 2, 3, 4, 5]})
def setValue(self, value: int):
self.value = value
@commandParser(subparsers=['devices'])
class Controller:
def __init__(self):
self.devices = {}
self.singleDevice = Device("singleDevice")
@command(help="Add a new device") #help message can also be explicitly set
def addDevice(self, name: str):
self.devices[name] = Device(name)
# Devices can have their own commands
class DeviceCommands:
@command(help="Set device value", completions={'value': [0, 1, 2, 3, 4, 5]})
def setValue(self, value: int):
self.value = value
controller = Controller()
app = SplitConsole(controller,"My CLI App")
app.start()
Advanced Features
Custom Type Conversion: - Override _convert_type for custom type handling - Support for bytes, hex strings, and more
Error Handling: - Type conversion errors - Missing required arguments - Invalid commands or paths
Command Help: - Auto-generated help from docstrings - Custom help messages per command
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cliify-0.1.3.tar.gz.
File metadata
- Download URL: cliify-0.1.3.tar.gz
- Upload date:
- Size: 15.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d034f0debccda104c4eda475804ead8250945c33e2610f5c32923a95cb06067
|
|
| MD5 |
c83b1ce9c30bb25ea6f0bef17d7cbacb
|
|
| BLAKE2b-256 |
ae4d08b98ed1dda3d4586ce1bd7ad46961990b3f1c322755114622b9a2daf4c0
|
File details
Details for the file cliify-0.1.3-py3-none-any.whl.
File metadata
- Download URL: cliify-0.1.3-py3-none-any.whl
- Upload date:
- Size: 20.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71aeb2dfc5f7ea1d58f27bdadad4742697b622c9cd46d13a605f7a8876d67b3b
|
|
| MD5 |
23f6ee06a1d471bd30815aab62342c20
|
|
| BLAKE2b-256 |
e98902a3a201d2921f3e6ac724735618708392379aee8314d19bf30b04274222
|