# 7. Integration Examples

The following are self-contained, copy-paste examples showing how to use `bitmath` with popular third-party libraries. These libraries are not installed by bitmath — install them separately before use.

## 7.1. argparse

The `argparse` module (part of the Python standard library) accepts command-line arguments as strings by default. The `type` parameter of `add_argument()` lets you supply a callable that converts a raw string into whatever type your application needs.

The snippet below defines a `BitmathType` callable and registers it as the type for a `--block-size` option so that users can write values like `--block-size 10MiB` and receive a `bitmath.MiB` object directly.

    import argparse
    import bitmath

    def BitmathType(value):
        """Convert a command-line string such as '10MiB' into a bitmath object."""
        try:
            return bitmath.parse_string(value)
        except ValueError:
            raise argparse.ArgumentTypeError(
                f"{value!r} is not a recognized bitmath unit string "
                "(examples: 10MiB, 1.5GiB, 500kB)"
            )

    def main():
        parser = argparse.ArgumentParser(
            description="Example script using a bitmath argument type"
        )
        parser.add_argument(
            "--block-size",
            type=BitmathType,
            required=True,
            help="Block size with unit, e.g. 10MiB",
        )
        args = parser.parse_args()
        print(f"Block size: {args.block_size}")
        print(f"In KiB:     {args.block_size.to_KiB():.2f}")

    if __name__ == "__main__":
        main()

Example run:

    $ python script.py --block-size 10MiB
    Block size: 10.0 MiB
    In KiB:     10240.00 KiB

    $ python script.py --block-size bad
    error: argument --block-size: 'bad' is not a recognized bitmath unit string (examples: 10MiB, 1.5GiB, 500kB)

### 7.1.1. argparse validation

Now say you want to perform some additional validation on this custom `BitmathType` unit. This is best done after the parsing is complete, not as part of the `BitmathType` implementation. This follows the advice outlined in the upstream `argparse` documentation.

> In general, the `type` keyword is a convenience that should only be used for simple conversions that can only raise one of the three supported exceptions. Anything with more interesting error-handling or resource management should be done downstream after the arguments are parsed.

First, parse the unit, allow `BitmathType` to handle the parsing validation. Second, perform your own context-aware validation. For example, you might set minimum or maximums and need to compare the parsed argument against them.

     1import argparse
     2import bitmath
     3
     4
     5def BitmathType(value):
     6    """Convert a command-line string such as '10MiB' into a bitmath object."""
     7    try:
     8        return bitmath.parse_string(value)
     9    except ValueError:
    10        raise argparse.ArgumentTypeError(
    11            f"{value!r} is not a recognized bitmath unit string "
    12            "(examples: 10MiB, 1.5GiB, 500kB)"
    13        )
    14
    15
    16def main():
    17    max_block_size = bitmath.GiB(1)
    18    parser = argparse.ArgumentParser(
    19        description="Example script using a bitmath argument type"
    20    )
    21    parser.add_argument(
    22        "--block-size",
    23        type=BitmathType,
    24        required=True,
    25        help="Block size with unit, e.g. 10MiB",
    26    )
    27    args = parser.parse_args()
    28
    29    if args.block_size > bitmath.GiB(1):
    30        raise ValueError(f"Provided block size {args.block_size} exceeds maximum {max_block_size}")
    31
    32    print(f"Block size: {args.block_size}")
    33    print(f"In KiB:     {args.block_size.to_KiB():.2f}")
    34
    35
    36if __name__ == "__main__":
    37    main()

Example run:

    $ python script.py --block-size 42GiB
    Traceback (most recent call last):
    File "script.py", line 37, in <module>
        main()
        ~~~~^^
    File "script.py", line 30, in main
        raise ValueError(f"Provided block size {args.block_size} exceeds maximum {max_block_size}")
    ValueError: Provided block size 42.0 GiB exceeds maximum 1.0 GiB

## 7.2. click

click is a popular command-line interface toolkit. Custom parameter types are implemented by subclassing `click.ParamType` and overriding `convert()`.

Install click before use:

    pip install click

    import click
    import bitmath

    class BitmathParamType(click.ParamType):
        """A click parameter type that accepts bitmath unit strings."""

        name = "SIZE"

        def convert(self, value, param, ctx):
            if isinstance(value, bitmath.Bitmath):
                return value
            try:
                return bitmath.parse_string(value)
            except ValueError:
                self.fail(
                    f"{value!r} is not a recognized bitmath unit string "
                    "(examples: 10MiB, 1.5GiB, 500kB)",
                    param,
                    ctx,
                )

    BITMATH = BitmathParamType()

    @click.command()
    @click.option(
        "--block-size",
        type=BITMATH,
        required=True,
        help="Block size with unit, e.g. 10MiB",
    )
    def main(block_size):
        """Example command using a bitmath click parameter type."""
        click.echo(f"Block size: {block_size}")
        click.echo(f"In KiB:     {block_size.to_KiB():.2f}")

    if __name__ == "__main__":
        main()

Example run:

    $ python script.py --block-size 10MiB
    Block size: 10.0 MiB
    In KiB:     10240.00 KiB

    $ python script.py --block-size bad
    Error: Invalid value for '--block-size': 'bad' is not a recognized bitmath unit string (examples: 10MiB, 1.5GiB, 500kB)

## 7.3. progressbar2

progressbar2 is a flexible terminal progress-bar library. The example below defines a custom widget that displays a data-transfer speed (bytes per second) in a human-readable bitmath unit, and demonstrates it with a simulated file download.

Install progressbar2 before use:

    pip install progressbar2

    import time
    import progressbar
    import bitmath

    class DataTransferSpeed(progressbar.widgets.FormatWidgetMixin,
                            progressbar.widgets.TimeSensitiveMixin):
        """Display transfer speed as a human-readable bitmath value per second."""

        def __call__(self, progress, data, **kwargs):
            elapsed = data.get("seconds_elapsed") or 0
            if elapsed <= 0 or data.get("value") is None:
                return "?? B/s"
            bytes_done = data["value"]
            speed = bitmath.Byte(bytes_done / elapsed).best_prefix()
            return f"{speed:.2f}/s"

    def simulate_download(total_bytes):
        widgets = [
            "Downloading: ",
            progressbar.Bar(),
            " ",
            progressbar.Percentage(),
            "  ",
            DataTransferSpeed(),
            " ",
            progressbar.ETA(),
        ]
        with progressbar.ProgressBar(
            max_value=total_bytes, widgets=widgets
        ) as bar:
            received = 0
            chunk = total_bytes // 50
            while received < total_bytes:
                time.sleep(0.05)
                received = min(received + chunk, total_bytes)
                bar.update(received)

    if __name__ == "__main__":
        # Simulate a 100 MiB download
        simulate_download(int(bitmath.MiB(100).to_Byte()))

Example run:

    Downloading: |####################| 100%  18.32 MiB/s ETA:  0:00:00