Skip to main content

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:

  1. 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

  1. Custom Type Conversion: - Override _convert_type for custom type handling - Support for bytes, hex strings, and more

  2. Error Handling: - Type conversion errors - Missing required arguments - Invalid commands or paths

  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cliify-0.1.11.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cliify-0.1.11-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

Details for the file cliify-0.1.11.tar.gz.

File metadata

  • Download URL: cliify-0.1.11.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.3

File hashes

Hashes for cliify-0.1.11.tar.gz
Algorithm Hash digest
SHA256 404cc59a9025c50cbad6d5036713e5d4ad6e56929c89c8ce1e76eca3d8707cf7
MD5 b697ea2035af9552136fd65c4cd6ad44
BLAKE2b-256 271e93a1400d0c0dfb7310360cb63bcd011267f33137816f8268bd465863d0c5

See more details on using hashes here.

File details

Details for the file cliify-0.1.11-py3-none-any.whl.

File metadata

  • Download URL: cliify-0.1.11-py3-none-any.whl
  • Upload date:
  • Size: 21.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.3

File hashes

Hashes for cliify-0.1.11-py3-none-any.whl
Algorithm Hash digest
SHA256 6e842d84e0a7085ce7ef9f54d84484afbb325828ba188b1e273f395b05950cb8
MD5 184861cd4ee50507ab5d3ddc23ba63b9
BLAKE2b-256 6dbeb8cdc6a0d12c42096c57c51b5d4b10e0a42eb052af41a81c7f4085d96191

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page