Using rudiments

Web Access Helpers

The rudiments.www module helps with handling web resources.

The context manager rudiments.www.url_as_file() can be used to make the content of an URL available as a local file, so it can be fed to things that only work with local filesystem paths. House-keeping is automatic, so the file is removed on leaving the context unless you removed or moved it yourself before that.

Security Helpers

Credentials Lookup

When using HTTP APIs or other secured web resources, you are confronted with the question how to enable your users to store their credentials in a secure but still convenient fashion. The rudiments.security.Credentials class tries to give an answer, by providing some common methods for credential lookup that occupy different spots in the secure vs. convenient spectrum. See Configuration of Authentication Credentials for details regarding usage from the viewpoint of an end-user and some information about the availabe credential providers.

To use the class, create a rudiments.security.Credentials object, passing in the target. Then to retrieve matching credentials, call the rudiments.security.Credentials.auth_pair() method.

access = Credentials('http://jane@doe.example.com')
username, password = access.auth_pair()

Note that this allows to only prompt the user for a password when it’s actually needed, but still create the credentials object early on, during some setup phase.

Humanized Input and Output

For accepting input from prompts and configuration files, and presenting values in a form easily parsed by humans, the rudiments.humanize module offers conversion functions for common data types.

For handling byte sizes in IEC binary units, use rudiments.humanize.bytes2iec() and rudiments.humanize.iec2bytes(). Examples:

>>> bytes2iec(1536), bytes2iec(10**9)
(u'   1.5 KiB', u' 953.7 MiB')
>>> bytes2iec(1536, compact=True)
u'1.5KiB'
>>> iec2bytes(1), iec2bytes('64k'), iec2bytes('1.234TiB')
(1, 65536, 1356797348675)

By default, the formatted values are suited for tabulated output (they’re all the same length); when passing compact=True, you’ll get a result that better fits into log messages.

To present lists of numbers in a compact form, collapsing consecutive ranges, rudiments.humanize.merge_adjacent() can be used.

>>> ', '. join(humanize.merge_adjacent(('9', 5, 10, 7) + tuple(range(5))))
u'0..5, 7, 9..10'

Python Runtime Support

Use the rudiments.pysupport module to access some helpers which hide internals of the Python interpreter runtime and provide an easier to use interface.

The functions rudiments.pysupport.import_name() and rudiments.pysupport.load_module() can be used for dynamic imports and adding a simple plug-in system to your application.

To help with keeping code portable between Python 2.7 and 3.x, the rudiments._compat module offers unified names and semantics for common features that differ between current and legacy Python versions. It is based on the module with the same name found in Jinja2.

Extensions to 3rd Party Libraries

The sub-package rudiments.reamed contains modules that extend the API of some outside library.

Note that you need to add the underlying package to your dependencies in addition to rudiments, in case you use one of the modules in that sub-package. rudiments itself does not publish any dependencies on them.

Where the extended package has a condensed public API (i.e. names are usually only imported from the package name), these modules can serve as a drop-in replacement, so you just have to change the import statement a little.

Extensions to Click

You can use the rudiments.reamed.click module as a drop-in replacement for Click, like this:

from rudiments.reamed import click

There are additional helper functions: rudiments.reamed.click.pretty_path() wraps rudiments.reamed.click.format_filename() to make a file system path presentable to humans, especially for logging purposes. The rudiments.reamed.click.serror() function prints an already styled, very visible error message, while using any arguments to format the message.

The rudiments.reamed.click.LoggedFailure exception can be used when you want to abort a command with a clearly visible error – the message is styled identically to what serror() produces, white bold text on a red background.

rudiments.reamed.click.AliasedGroup allows you to define alias names for commands you defined via the usual annotatons. Here is an example that maps the ls alias to the official list command name:

from rudiments.reamed import click

class SmurfAliases(click.AliasedGroup):
    """Alias mapping for 'smurf' commands."""
    MAP = dict(
        ls='list',
    )


@cli.group(cls=SmurfAliases)
def smurf():
    """Management of smurfs."""


@smurf.command(name='list')
def smurf_list():
    """A command that lists smurfs."""
    # …

Finally, the biggest addition is a default configuration parsing machinery in the rudiments.reamed.click.Configuration class. It should be instantiated in your root command, passing in the (optional) name of a specific configuration file, or a path of such files.

@click.group()
@click.option('-c', '--config', "config_paths", metavar='FILE',
              multiple=True, type=click.Path(), help='Load given configuration file(s).')
@click.pass_context
def cli(ctx, config_paths=None):
    """Some command line tool."""
    config.Configuration.from_context(ctx, config_paths)

The prepared configuration object is then available to any sub-command via the context, as ctx.obj.cfg. For more details, see the rudiments.reamed.click.Configuration documentation.