Skip to content

baldulin/ackermann

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ackermann - Command/Config Library

This library simplifies the startup process of a more complicated python project with multiple subcommands, a complicated configuration, aswell as certain targets that need to be run or initialized before starting commands in a specific order.

The point of this library is to easily provide a way to setup a commandline interface using logging and argparse and contextvars that looks like this:

my-programm -vvv -c <config_file.py> my-custom-command --my-custom-options

While providing a default way of starting the programm you might implemented way. Just have a look at ackermann.units. The main purpose of this library is just to provide an extentable way to implement commands and startup targets.

Usage

The most simple setup is just to call ackermann.run in your __main__ file, like so:

from ackermann import run
from ackermann.units import run_command

run(targets=[run_command])

Now if you execute your module with python -m <module> you can specify a config with -c change the verbosity level with -v and specify a subcommand.

Commands

If you want to write your own command you can do so:

from ackermann import Command, Config
from argparse import ArgumentParser
from time import sleep


class MyCommand(Command):
    name = "my-command"
    @classmethod
    def get_arguments(cls, parser: ArgumentParser):
        parser.add_argument("-s", "--skip", action="store_true")

    def run(self, config: Config):
        if config["ARGS"].skip:
            print("You wanted to skip")
        else:
            print("We are doing it as you are not skipping") 
            sleep(1000)

If you run your program as suggested you are able to run this command with:

python -m <project> my-command -s

The same works for async commands aswell:

from ackermann import Command, Config
from argparse import ArgumentParser
from asyncio import sleep


class MyAsyncCommand(Command):
    name = "my-async-command"
    is_async = True

    @classmethod
    def get_arguments(cls, parser: ArgumentParser):
        parser.add_argument("-s", "--skip", action="store_true")

    async def async_run(self, config: Config):
        if config["ARGS"].skip:
            print("You wanted to skip")
        else:
            print("We are doing it as you are not skipping") 
            await sleep(1000)

running this command would look like this:

python -m <project> my-async-command -s

Targets / Units

More complicated commands might want to reuse certain parts of code to initialize their project in the correct way. For this ackermann has ConfigUnits. These work pretty similiar to context managers around your command.

A unit might be defined like this:

from ackermann import Config, config_unit

@config_unit
async def do_sth(config: Config):
    print("I want to do sth before my command starts")
    yield
    print("Now I need to cleanup as the command finished")

This unit will not run by default, but it must be explicitly stated in the command. Using the targets variable. Config units might depend on each other, might conflict with one another or must be run in a certain order. They might also force the programm to be run in an async context or on multiple processors.

Signals

If your programm needs to signal certain parts of code about it's state you might also add signals. To your config by default before executing a command ackermann will signal ready and after exiting a command it will signal stopping. This might be used to signal systemd in case of using a notify service.

Config Variables

All commands, units and signals are called with the current instance of Config which itself stores configuration variables that might be stored in a python module supplied with the -c flag. If you want to explicitly tell the programm about these variables you can use the small wrapper ackermann.ConfigVar around contextvars.ContextVar which allows you to specify the format, type, and default value for a config variable.

These variables might then be imported in your programm without worrying where the get the correct value like so:

from ackermann import ConfigVar

config_my_config_var = ConfigVar("MY_CONFIG_VAR", description="Does something", type=int, default=0)

# At some point in the code

def do_sth():
    my_config_var_value = config_my_config_var.get()

One benefit of using this interface is that you can easily check the variables set when starting the programm with the -V flag.

About

Python program startup/command manager

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages