Skip to content

igrek51/nuclear

Repository files navigation

☢️ Nuclear

GitHub version (latest SemVer) PyPI Github Pages Documentation Status codecov GitHub Workflow Status

Nuclear is a binding glue for CLI applications. It consists of tools for building CLI applications in Python, including:

CLI Demo

from nuclear import CliBuilder

cli = CliBuilder()

@cli.add_command('hello')
def say_hello(name: str, decode: bool = False, repeat: int = 1):
    """
    Say hello
    :param decode: Decode name as base64
    """
    message = f"I'm a {b64decode(name).decode() if decode else name}!"
    print(' '.join([message] * repeat))

@cli.add_command('calculate', 'factorial')
def calculate_factorial(n: int):
    """Calculate factorial"""
    print(reduce(lambda x, y: x * y, range(1, n + 1)))

@cli.add_command('calculate', 'primes')
def calculate_primes(n: int):
    """List prime numbers using Sieve of Eratosthenes"""
    print(sorted(reduce((lambda r, x: r - set(range(x**2, n, x)) if (x in r) else r), range(2, n), set(range(2, n)))))

cli.run()

See demo.py for a complete example.

Installation

python3 -m pip install --upgrade nuclear

You need Python 3.10 or newer.

📜 Sublog

Sublog is a nuclear's contextual logging system that allows you to:

  • display variables besides log messages: logger.debug('message', airspeed=20),
  • wrap errors with context: with add_context('ignition'),
  • catch errors and show traceback in a concise, pretty format: with error_handler().
from nuclear.sublog import logger, error_handler, add_context

with error_handler():
    logger.debug('checking engine', temperature=85.0, pressure='12kPa')
    with add_context('ignition', request=42):
        logger.info('ignition ready', speed='zero')
        with add_context('liftoff'):
            raise RuntimeError('explosion')

sublog demo

Context logger

Use nuclear.sublog.logger to log message with a pretty format out of the box.

Pass additional context variables as keyword arguments to display them in the log message.

from nuclear.sublog import logger

logger.info('info log')
logger.debug('debug log', var1=1, var2='two')
logger.info('not great not terrible', radioactivity=3.6)
logger.error('this is bad')
logger.exception(RuntimeError('this is worse'))

Error handler

Use nuclear.sublog.error_handler to catch errors and show traceback in a concise, pretty format.

from nuclear.sublog import error_handler

with error_handler():
    raise RuntimeError('explosion')

Wrapping context

Use nuclear.sublog.add_context to wrap code with additional context information. This will be included in in the log message, if an error occurs.

from nuclear.sublog import add_context

with add_context('reloading plugins'):
    with add_context('loading config'):
        raise RuntimeError('file is missing')

This will produce an error with the following message:

reloading plugins: loading config: file is missing

Note that while each individual part of the message may not provide a comprehensive explanation of the error, when combined, the whole message becomes highly informative. This is the core principle behind enriching errors with context.

🚀 Nuke - Task Runner

Nuke is a lightweight task runner built into Nuclear for automating project tasks. It serves as a replacement for Makefile.

Define tasks as simple Python functions in a nukefile.py:

from nuclear import nuke, logger

class Config:
    src_path: str = '/opt/dump'
    dst_path: str = '/media/user/DRIVE/'
    dry: bool = False

config, sh = nuke.init(Config)

def push():
    sh(f"rsync -avh --delete --size-only --info=progress2 '{src_path}/' '{dst_path}'")

def convert():
    sh(f'ffmpeg -i "{src_path}/input.mp4" "{dst_path}/audio.mp3"')

if __name__ == '__main__':
    nuke.run()

Run tasks from command line:

nuke push
# or
./nukefile.py push

# see available tasks
nuke
./nukefile.py

# dry run mode
nuke push --dry

# override config parameters
nuke convert --src_path=/src/dump

See Nuke documentation for detailed guide.

🐌 Shell utilities

Nuclear provides utilities for running system shell commands.

Basic usage:

from nuclear import shell

window_id: str = shell('xdotool getactivewindow')

shell function captures the stdout & stderr output of the shell command and returns it as a string. It may also print live stdout in real time (line by line) and capture output in case of errors.

It has a lot of possibilities thanks to its parameters:

  • cmd: str - shell command to run
  • workdir: Optional[Path] = None - working directory for the command
  • print_stdout: bool = False - whether to print live stdout in real time (line by line) from a subprocess
  • print_log: bool = False - whether to print a log message about running the command
  • raw_output: bool = False - whether to let subprocess manage stdout/stderr on its own instead of capturing it
  • independent: bool = False - whether to start an independent process that can outlive the caller process
  • output_file: Optional[Path] = None - optional file to write the output in real time

It returns the stdout of the command combined with stderr. In case of non-zero command exit code, shell raises CommandError exception.

About

Binding glue for CLI Python applications

Topics

Resources

License

Stars

Watchers

Forks

Contributors