-2

I am trying to write a Nagios style check to use with Nagios. I have working script that takes in something like -w 15 -c 10 and interprets that as "Warning at 15%, Critical at 10%". But I just realized that in the built-in Nagios plugins, the same arguments would mean "Warning at 15MB, Critical at 10MB"; I would instead need to enter -w 15% -c 10% to get the above behavior.

So my question is, what is the best way to make my script behave like the built-in Nagios scripts? The only way I can think of is accepting the argument as a string and parsing it, but is there a neater way?

Vertexwahn
  • 7,709
  • 6
  • 64
  • 90
DDauS
  • 105
  • 1
  • 2
  • 11
  • Whether you can parse it in a custom `type` function, or have to do it after, depends on the details of the parsing. Explain what output you want from the parsing. Do you just divide by 100 in the % case, and multiply by 10^6 in the other? – hpaulj Jan 19 '17 at 18:01

2 Answers2

2

You can use your own class as type for the arguments:

import argparse

class Percent(object):
    def __new__(self,  percent_string):
        if not percent_string.endswith('%'):
            raise ValueError('Need percent got {}'.format(percent_string))
        value = float(percent_string[:-1]) * 0.01
        return value

parser = argparse.ArgumentParser(description="with percent")
parser.add_argument('-w', '--warning', type=Percent)
parser.add_argument('-c', '--critcal', type=Percent)

args = parser.parse_args()
print(args.warning)

Output:

python parse_percent.py  -w 15%
0.15

python parse_percent.py  -w 15
usage: parse-percent.py [-h] [-w WARNING] [-c CRITCAL]
parse-percent.py: error: argument -w/--warning: invalid Percent value: '15'

Version that works with percent or MB

class Percent(object):
    def __new__(self,  percent_string):
        if percent_string.endswith('%'):
            return float(percent_string[:-1]), 'percent'
        else:
            return float(percent_string), 'MB'

parser = argparse.ArgumentParser(description="with percent")
parser.add_argument('-w', '--warning', type=Percent)
parser.add_argument('-c', '--critcal', type=Percent)

args = parser.parse_args()
value, unit = args.warning
print('{} {}'.format(value, unit))

Output:

python parse_percent.py -w 15
15.0 MB
python parse_percent.py -w 15%
15.0 percent
Mike Müller
  • 82,630
  • 20
  • 166
  • 161
  • Well, the behavior of the Nagios script is such that `-c 15` means 15 MB and `-c 15%` will mean 15%. It takes in both. Ideally, is there a way to make argparse take in different types for the same flag, and have Python decide the right type based on what data it gets? – DDauS Jan 19 '17 at 12:57
  • I think it is better to write the `percent` type as a plain function. `__new__` (as opposed to `__init__` is normally only used when the class definition requires some special behavior. Here is just seems to make the class behave like a plain function. – hpaulj Jan 19 '17 at 17:42
  • `@DDauS` - deciding between the `MB` and the `%` meaning is the job of this custom `type` function. It takes a string, and returns something. What it returns is for you to decide. – hpaulj Jan 19 '17 at 17:45
  • I'd like to use that with `action="append_const"` to store a list which apparently doesn't allow to have `type` argument. Any workaround ? – Enissay Sep 14 '20 at 23:42
2

This is the type function which I believe behaves the same as @Mike's class:

def percent(astr):
    if astr.endswith('%'):
        return float(astr[:-1]), 'percent'
    else:    
        return float(astr), 'MB'

parser = argparse.ArgumentParser(description="with percent")
parser.add_argument('-w', '--warning', type=Percent)
parser.add_argument('-c', '--critcal', type=percent)

args = parser.parse_args()
print(args)

testing:

1058:~/mypy$ python3 stack41741065.py 
Namespace(critcal=None, warning=None)

1059:~/mypy$ python3 stack41741065.py -w 14 -c 14
Namespace(critcal=(14.0, 'MB'), warning=(14.0, 'MB'))

1059:~/mypy$ python3 stack41741065.py -w 14% -c 14%
Namespace(critcal=(14.0, 'percent'), warning=(14.0, 'percent'))

1059:~/mypy$ python3 stack41741065.py -w bad
usage: stack41741065.py [-h] [-w WARNING] [-c CRITCAL]
stack41741065.py: error: argument -w/--warning: invalid Percent value: 'bad'

1100:~/mypy$ python3 stack41741065.py -c bad
usage: stack41741065.py [-h] [-w WARNING] [-c CRITCAL]
stack41741065.py: error: argument -c/--critcal: invalid percent value: 'bad'

type just has to be a callable that takes a string, and returns a value. Here it is returning a tuple, which the store Action just puts in the namespace. If the callable returns a ValueError, TypeError or argparse.ArgumentTypeError, the error display should be the same. In these examples the initial error is the ValueError produced by float('bad'). The default error message is uses the callable's name (Percent v percent).

An example of post-parsing parsing is:

if args.o is not None:
    try:
        args.o = percent(args.o)
    except ValueError:
        parser.error('invalid args.o value')
print(args)

100:~/mypy$ python3 stack41741065.py
Namespace(critcal=None, o=None, warning=None)
Namespace(critcal=None, o=None, warning=None)

1107:~/mypy$ python3 stack41741065.py -o 14
Namespace(critcal=None, o='14', warning=None)
Namespace(critcal=None, o=(14.0, 'MB'), warning=None)

1107:~/mypy$ python3 stack41741065.py -o 14%
Namespace(critcal=None, o='14%', warning=None)
Namespace(critcal=None, o=(14.0, 'percent'), warning=None)

1107:~/mypy$ python3 stack41741065.py -o bad
Namespace(critcal=None, o='bad', warning=None)
usage: stack41741065.py [-h] [-w WARNING] [-c CRITCAL] [-o O]
stack41741065.py: error: invalid args.o value

argparse.FileType is an example of a type function factory class.

hpaulj
  • 221,503
  • 14
  • 230
  • 353