Plumbum Documentation

repository·master·Indexed 25 days ago

https://github.com/tomerfiliba/plumbum

A Python shell combinators library that allows developers to write cross-platform, Pythonic code for command execution, piping, and redirection. Plumbum provides tools for building CLI applications via `plumbum.cli`, managing local and remote machines (including SSH and Paramiko), and executing commands asynchronously using `asyncio` with `async_local` and `AsyncSshMachine`.

Tokens
26.1K
Snippets
48
Records
196
Agent score
84%

What's inside plumbum

  1. Overview of Plumbum Features

    master

    Plumbum is a library for writing shell-like programs in Python. Key features include:

    • Shell-like syntax: Mimics shell combinators (pipes, etc.) in a Pythonic way.
    • Command Execution: Supports both local and remote (over SSH) command execution.
    • File-system Abstraction: Provides local and remote paths that work seamlessly.
    • Environment & Directory Manipulation: Easy handling of working directories and environment variables.
    • CLI Toolkit: A programmatic toolkit for building command-line applications.
    • ANSI Colors: Quick access to terminal colors and styles.
  2. Understand the Colorlib class hierarchy

    master

    The Colorlib system is built around three primary classes:

    1. Color: Represents a color using various formats (RGB, hex, 256-color names, or simple names). It can be initialized with an fg (True/False) argument to specify if it is a foreground color. An uninitialized Color instance represents the reset color.
    2. Style: The core class that holds two Color objects (foreground and background) and a dictionary of attributes. This is the object produced by factories.
    3. StyleFactory: A factory class used to provide simple access to Style classes. You initialize it with a specific Style subclass to create a usable color/style object.

    Most users interact with instances created by factories rather than the classes themselves.

  3. Access plumbum.commands submodules

    master

    The plumbum.commands package is organized into several specialized submodules for managing shell operations:

    • plumbum.commands.base: Core command functionality.
    • plumbum.commands.daemons: Tools for managing daemon processes.
    • plumbum.commands.modifiers: Objects for altering command execution behavior (e.g., BG, NOHUP, TEE).
    • plumbum.commands.processes: Utilities for process management.
    • plumbum.commands.async_: Asynchronous command execution capabilities.
  4. Execute local commands with plumbum.local

    master

    Use the local object to access and execute commands on your local machine. You can access commands via local.cmd.command_name or by using bracket notation local["path/to/executable"] for specific paths or unusual names. Calling the command object executes it and returns the output as a string.

    from plumbum import local
    
    # Access via cmd attribute
    local.cmd.ls()
    
    # Access via specific path
    notepad = local["c:\\windows\\notepad.exe"]
    notepad()
  5. Perform safe color manipulations with context managers

    master

    To ensure terminal colors are automatically reset (even if an exception occurs), use colors, colors.fg, or colors.bg in a with statement.

    • with colors: resets all properties (foreground, background, and modifiers).
    • with colors.fg: resets only foreground colors.
    • with colors.bg: resets only background colors.
    • with colors.red: resets only the red foreground color.

    Alternatively, use colors.print("string") or wrap strings using colors["string"] or colors.wrap("string") for safe, one-off styled output.

  6. Execute remote commands via SshMachine

    master

    Plumbum supports remote command execution over SSH using SshMachine. It is compatible with OpenSSH, PuTTY (on Windows), and Paramiko. You can use the same API as local to interact with the remote machine.

    from plumbum import SshMachine
    
    remote = SshMachine("somehost", user="john", keyfile="/path/to/idrsa")
    r_ls = remote["ls"]
    
    with remote.cwd("/lib"):
        (r_ls | grep["0.so.0"])()
  7. Extend Colorlib by subclassing Style

    master

    You can add support for new output formats (like HTML, Markdown, or custom terminal emulators) by subclassing Style and defining a __str__ method.

    To implement a new style, you should:

    1. Define attribute_names as a dictionary mapping attribute keys to their output tags.
    2. Define an end string for closing styles.
    3. Implement __str__ to handle the logic for foreground (self.fg), background (self.bg), and attributes (self.attributes).

    Example implementation for HTML:

    class HTMLStyle(Style):
        attribute_names = dict(bold='b', li='li', code='code')
        end = '<br/>\n'
    
        def __str__(self):
            result = ''
            if self.bg and not self.bg.reset:
                result += f'<span style="background-color: {self.bg.hex_code}">'
            if self.fg and not self.fg.reset:
                result += f'<font color="{self.fg.hex_code}">'
            for attr in sorted(self.attributes):
                if self.attributes[attr]:
                    result += '<' + self.attribute_names[attr] + '>'
    
            for attr in reversed(sorted(self.attributes)):
                if not self.attributes[attr]:
                    result += '</' + self.attribute_names[attr].split()[0] + '>'
            if self.fg and self.fg.reset:
                result += '</font>'
            if self.bg and self.bg.reset:
                result += '</span>'
    
            return result
    
    htmlcolors = StyleFactory(HTMLStyle)
    class HTMLStyle(Style):
        attribute_names = dict(bold='b', li='li', code='code')
        end = '<br/>\n'
    
        def __str__(self):
            result = ''
    
            if self.bg and not self.bg.reset:
                result += f'<span style="background-color: {self.bg.hex_code}">'
            if self.fg and not self.fg.reset:
                result += f'<font color="{self.fg.hex_code}">'
            for attr in sorted(self.attributes):
                if self.attributes[attr]:
                    result += '<' + self.attribute_names[attr] + '>'
    
            for attr in reversed(sorted(self.attributes)):
                if not self.attributes[attr]:
                    result += '</' + self.attribute_names[attr].split()[0] + '>'
            if self.fg and self.fg.reset:
                result += '</font>'
            if self.bg and self.bg.reset:
                result += '</span>'
    
            return result
    
    htmlcolors = StyleFactory(HTMLStyle)
  8. Use TypedEnv as an abstraction layer for environment variables

    master

    Use TypedEnv to decouple your application logic from specific environment variable names. This is particularly useful for CI/CD pipelines where variable names might change depending on the provider (e.g., Travis vs. Jenkins). You can use Python @property decorators within your TypedEnv class to normalize different environment variables into a single consistent attribute.

    from plumbum.typed_env import TypedEnv
    
    class CiBuildEnv(TypedEnv):
        is_travis = TypedEnv.Bool("TRAVIS", default=False)
        _travis_job_id = TypedEnv.Str("TRAVIS_JOB_ID")
        _jenkins_job_id = TypedEnv.Str("BUILD_ID")
    
        @property
        def job_id(self):
            return self._travis_job_id if self.is_travis else self._jenkins_job_id
  9. Create a CLI application with plumbum.cli.Application

    master

    To build a command-line application, create a class that extends plumbum.cli.Application. Implement a main() method to handle positional arguments. You can expose command-line switches by defining class attributes using plumbum.cli.switches. To execute the application, call the class method run().

    Key components:

    • main(self, *args): The core logic of your application. Positional arguments from the CLI are passed here.
    • run(): The entry point that instantiates the class, parses arguments, and calls main().
    • switches: Attributes defined on the class that represent CLI flags or options.
    from plumbum import cli
    
    class MyApp(cli.Application):
        verbose = cli.Flag(["v", "verbose"], help="If given, I will be very talkative")
    
        def main(self, filename):
            print(f"I will now read {filename}")
            if self.verbose:
                print("Yadda " * 200)
    
    if __name__ == "__main__":
        MyApp.run()
  10. Use async commands in Plumbum

    master

    Plumbum supports asyncio for running commands using async/await syntax. There are three primary ways to access async commands:

    1. Direct import: Use plumbum.async_cmd when command names are known at import time. This follows the same pattern as plumbum.cmd.
    2. Using async_local: Use from plumbum import async_local for more dynamic command lookup or when you need more control.
    3. Dynamic access: Use getattr on the plumbum.async_cmd module when command names are determined at runtime.

    To get the full result object (including returncode, stdout, and stderr) instead of just the output string, use the .run() method.

    from plumbum.async_cmd import ls, grep, echo
    import asyncio
    
    async def main():
        # Simple command execution (returns stdout string)
        result = await ls("-la")
        print(result)
    
        # Get full result object
        result_obj = await ls.run(["-la"])
        print(f"Return code: {result_obj.returncode}")
        print(f"Output: {result_obj.stdout}")
    
    asyncio.run(main())
  11. Manipulate working directories

    master

    You can change the working directory for a block of code or for a specific command instance.

    • Context Manager: Use with local.cwd(path): to change the directory for a block of code.
    • Command-specific: Use the .with_cwd(path) method on a command object to create a new command instance that always runs in that directory. This is thread-safe.