Skip to content

Latest commit

 

History

History
232 lines (193 loc) · 7.2 KB

README.md

File metadata and controls

232 lines (193 loc) · 7.2 KB

Moxom

PyPI version

Python library to build simple user-friendly CLI API for your Python scripts.
Allows mapping commands to classes, class methods and functions.
Supports both def and async def.

Let's assume we have script dumb_ci with next class:

from asyncio import sleep

class Computer:
    def __init__(self, computer_name: str = "Default"):
        self.computer_name = computer_name

    async def compile_code(self, project_name: str = "TestProject"):
        await sleep(2) # build something
        print(f"Built finished for {self.computer_name} {project_name}")

And next function:

from asyncio import sleep

async def deploy_code(project_name: str = "TestProject"):
     await sleep(1)
     print(f"Deployed {project_name}")

With this library you are able to create simple API for CLI.

./dumb_ci computer compile_code

Library allows passing parameters.

./dumb_ci computer '"Build server #2"' compile_code '"MegaProject"'

You can order commands using basic shell tools

./dumb_ci computer compile_code && ./dumb_ci deploy_code
./dumb_ci computer compile_code '"Project1"' & ./dumb_ci computer compile_code '"Project2"'

Additionally, you can use operators and (asynchronous), then (synchronous) and parentheses.

./dumb_ci computer compile_code then deploy_code
./dumb_ci (computer compile_code then deploy_code) and (computer '"2"' compile_code '"Project2"' then deploy_code '"Project2"')

To list all available commands there is a help operator.

./dumb_ci help
> computer
> computer compile_code
> deploy_code

To obtain help for a specific command just add help before command. If no help text provided in command definition, the one received with Python help command will be used.

./dumb_ci deploy_code
> Deploys code
./dumb_ci computer compile_code
> Compiles code

Integration

Assuming we have class Computer and function deploy_code from example above entire integration would look like this:

import sys
from moxom import Moxom, Routine, create_routines

routines = create_routines(            # Utility function to create Dict[str, Routine]
    Routine(                           # Class representation of a command in CLI
        "deploy_code",                 # Command name
        deploy_code,                   # Function to call
        help="Deploys code to AWS"     # Text for help
    ),
    Routine(                           # Class representation of a command in CLI
        "computer",                    # Command name
        Computer,                      # Constructor of class Computer and it is a function 
        subroutines=create_routines(   # As we want to invoke methods of Computer, we can specify subroutines 
            Routine(                   # which are represented by Computer class methods
                "compile_code",        # Subroutine name. "computer compile_code"
                Computer.compile_code, # Method of Computer to be called,
                help="Compiles code"   # which will receive instance created by "computer" as `self`
            )                          
        )
    )
)

if __name__ == '__main__':
    moxom = Moxom(routines)
    arguments = sys.argv[1:]
    moxom.execute(" ".join(arguments))

To increase comfortability you may want to have simple interactive mode

if __name__ == '__main__':
    moxom = Moxom(routines)

    arguments = sys.argv[1:]

    if len(arguments) == 0:
        # interactive mode
        print("Interactive mode")
        while True:
            moxom.execute(input("> "))
    else:
        # single line mode
        moxom.execute(" ".join(arguments))

Example

Example of usage and integration is here.

Basic usage

To integrate library into existing Python project, just import as in next line:

from moxom import Moxom, Routine, create_routines

Moxom

Moxom - library entry-point.
Expects to receive Dict[str, Routine] on construction. Optionally you can pass environment file path.
Call execute(line) to process a single line of input.

Routine

Routine - class representation of a single command.
It must have a name and a function to call.
Optionally you may set subroutines and help text.

create_routines

create_routines - Utility function to create Dict[str, Routine].

Operators

And

Allows running commands asynchronously, & in Shell, maps to asyncio.gather.

# Starts computers simultaneously
computer "1" start and computer "2" start

Then

Allows running commands consequently, && in Shell, maps to consequent code with await.

# Starts computer 1, waits for it to start, and then starts computer 2
computer "1" start then computer "2" start

Parentheses

To specify higher precedence of operation, you can use parentheses.

# Start computer 1 and computer 2 simultaneously.
# Wait for both to start.
# Then shut down both computers 1 and 2 simultaneously.
(computer "1" start and computer "2" start) then (computer "1" shutdown and computer "2" shutdown)

Help

To list all available commands there is a help operator.

./dumb_ci help
> computer
> computer compile_code
> deploy_code

To obtain help for a specific command just add help before command. If no help text provided in routine definition, the one received with Python help command will be used.

./dumb_ci deploy_code
> Deploys code
./dumb_ci computer compile_code
> Compiles code

Declarations

Commands and variables can be declared with = operator.
It can be a declaration of an argument.

> mega_project = "MegaProject"

# returns "MegaProject"
> mega_project

Or of a function.

> comp2_mega_full_ci = computer "Comp2" compile_code "MegaProject" then deploy_code "MegaProject"

# runs code
> comp2_mega_full_ci   

Additionally, you can provide arguments for a function.

> full_ci comp proj = computer comp compile_code proj then deploy_code proj

# runs full ci on Comp2 for MegaProject
> full_ci "Comp2" "MegaProject" 

# declares a function
> comp2_mega_full_ci = full_ci "Comp2" "MegaProject"

# runs full ci on Comp2 for MegaProject
> comp2_mega_full_ci

Known issues

When strings are passed from shell to python, shell removes quotes.
./dumb_ci computer "Build server #2" compile_code is parsed as computer Build server #2 compile_code and leads to exception.

Using from Shell you have to wrap strings twice with double quotation marks, or with quotes and escaping or by using both single quotes and double quotes:

./dumb_ci computer '"Build server #2"' compile_code

Environment

If you want to create simple scenarios with Moxom syntax or just to specify variables on script startup, you can create a file with commands, which will be executed on Moxom initialization.

env_file = os.path.join(os.path.dirname(__file__), "example_mox_env.ini")
moxom = Moxom(routines, moxom_env=env_file)

You can find example of environment file in example_mox_env.ini and integration in example module.