Restrict input string length in argparse add_argument - python-3.x

I want to add an argument of type string but restrict the length of the string to 1.
group.add_argument(
"--test_param",
dest="test_param",
type=str,
required=True,
default=None,
)
Is there any way I can do it here without having to raise an error by checking len(test_param) later?

You can try using a lambda function in type
Ex:
import argparse
group = argparse.ArgumentParser(description='Data.')
group.add_argument(
"--test_param",
dest="test_param",
type=lambda x: x if x.isalpha() and len(x) <=1 else False, #Sample Function
required=True,
default=None,
)
args = group.parse_args()
value = getattr(args, 'test_param')
if value:
print(value)
else:
print("error!!!")
Output:
python sample.py --test_param fddd #->>error!!
python sample.py --test_param f #->>f

You can do this with the type parameter of add_argument. The recommended way is to raise an exception during the type check:
import argparse
import sys
parser = argparse.ArgumentParser()
def len_gt_0(s):
if len(s) > 0:
return s
raise argparse.ArgumentTypeError("Value must have length greater than 0")
parser.add_argument("--foo", type=len_gt_0, required=True)
opts = parser.parse_args(sys.argv[1:])
print(f"{ opts.foo = }")
Output:
$ python3.8 /tmp/foo.py --foo=abc
opts.foo = 'abc'
$ python3.8 /tmp/foo.py --foo=
usage: foo.py [-h] --foo FOO
foo.py: error: argument --foo: Value must have length greater than 0
$ python3.8 /tmp/foo.py
usage: foo.py [-h] --foo FOO
foo.py: error: the following arguments are required: --foo

You can use the choices argument available while adding a argument for example in my code i just want to select from 1 or 2. I used the following snippet as follows:
parser.add_argument(
"report",
type=int,
metavar="[report #]",
help="1. for Annual Max/Min Temperature 2. for Hottest day of each year",
choices=[1, 2]
)
it will restrict the input from these choices only and will show the error result if you have given out of these choices like this:
usage: [report#] [data_dir]
[report #]
1 for Annual Max/Min Temperature
2 for Hottest day of each year
[data_dir]
Directory containing weather data files
main.py: error: argument [report #]: invalid choice: 3 (choose from 1, 2)
If you don't want to add the choices you can use lamda or a function for validating the data. You can get help from my example below:
parser.add_argument(
"data_dir",
type=check_directory_availability,
metavar="[data_directory]",
help="Directory containing weather data files",
)
def check_directory_availability(dir_name):
if not os.path.isdir(dir_name):
raise NotADirectoryError("Provided directory name doesn't exists")
return dir_name
Hope so it may help you.

Related

Python3: how to use module 'Import click' & Parsing command line

I am a hobby radio amateur [G6SGA] not a programmer but I do try. :)
using python3. I am trying to do the following and really can't get my head around - argparse and ended up trying to use 'Import click'. Still can't get my head around so here I am. Any all (polite) :) suggestions welcome.
I wish to ---
cmd line> python3 scratch.py [no options supplied]
output> "Your defaults were used and are:9600 and '/dev/ttyAMA0' "
or
cmd line> python3 scratch.py 115200 '/dev/ttyABC123'
output> "Your input values were used and are: 115200 and '/dev/ttyAMA0'"
so a command line that will take [or NOT] argument/s. store the argument to a variable in the code for future use.
This some of what I have tried: Yes I accept it's a mess
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
# ========================
# Include standard modules
# import click
# baud default = 9600
# port default = "/dev/ttyAMA0"
import click
#click.command()
# #click.option('--baud', required = False, default = 9600, help = 'baud rate defaults to: 9600')
# #click.option('--port', required = False, default = '/dev/ttyAMA0', help = 'the port to use defaults to: /dev/ttyAMA0')
#click.option('--item', type=(str, int))
def putitem(item):
click.echo('name=%s id=%d' % item)
def communications():
""" This checks the baud rate and port to use
either the command line supplied item or items.
Or uses the default values
abaud = 9600 # default baud rate
b=abaud
aport = "/dev/ttyAMA0"
p=aport
print(f"abaud = {b} and aport = {p}")
"""
# now I wish to check if there were supplied values
# on the command line
# print(f"Baud supplied {click.option.} port supplied {port}" )
if __name__ == '__main__':
putitem() # communications()
The code I have used to work this all out is below, I hope it helps somebody. Any better ways or mistakes please advise.
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
import click
from typing import Tuple
# Command Line test string: python scratch_2.py -u bbc.co.uk aaa 9600 bbb ccc
myuri = "" # This is a placeholder for a GLOBAL variable -- take care!
list1 = [] # This is a placeholder for a GLOBAL variable -- take care!
#click.command(name="myLauncher", context_settings={"ignore_unknown_options": True})
#click.option('--uri', '-u', type=click.STRING, default=False, help ="URI for the server")
#click.argument('unprocessed_args', nargs = -1, type = click.UNPROCESSED)
def main(uri: str, unprocessed_args: Tuple[str, ...]) -> None:
# ==================== Checking the command line structure and obtaining variables
global myuri # define the use of a GLOBAL variable in this function
temp = list((str(j) for i in {unprocessed_args: Tuple} for j in i)) # sort out the command line arguments
res = len(temp)
# printing result
print("")
for e in range(0 ,res): # list each of the command line elements not including any uri
print("First check: An input line Tuple element number: " + str(e) +": " + str(temp[e])) # elements base 0
# ==================== deal with any URI supplied -- or NOT
if uri is False: #if --uri or -u is not supplied
print("No uri supplied\n")
print("The input line tuple list elements count: " + str(res))
# set a defaul GLOBAL value of myuri if it is not supplied
myuri = "https://192.168.0.90:6691/" #
else:
print("\nThe input line tuple list elements count : " + str(res) + " and we got a uri")
myuri = uri # set the GLOBAL value of myuri if the uri is
print(f"which is: {uri}, and therefore myuri also is: {myuri}") # a temp print to prove the values of the GLOBAL variable 'myuri'
# ==============================================================================================
# Testing choice of baud rate on command line
db_list = {
'4800': 'TEST48',
'9600': 'TEST96',
'19200': 'TEST19',
'38400': 'TEST38',
'57600': 'TEST57',
'115200': 'TEST11',
}
# Print databases ----- db_list ----- listed in dictionary
print("\nDatabases:")
for e in range(0 ,res) :
""" list each of the command line elements not including any uri
print("Second Check: An input line Tuple element number: " + str(e) +": " + str(temp[e]))
elements base 0 """
if str(temp[e]) in db_list.keys() :
print(f"The index of db contains {str(temp[e])}, The index refers to: {db_list[str(temp[e])]}")
if __name__ == "__main__":
# pylint: disable=no-value-for-parameter, unexpected-keyword-arg
main()

Executing argparse Arguments in order they are passed in terminal / cmd

I have a data augmentation script that has a class with a bunch of optional methods that are triggered by argparse arguments. I am curious how I can structure my code to process the argparse commands based on the order they are passed in from the terminal.
Goal: If I were to pass arguments as: python maths.py --add --multiply I would want it to add 10 first then multiply by 5 second.
If I were to pass arguments as: python maths.py --multiply --add I would want it to multiply 5 first then add 10.
For example:
class Maths:
def __init__(self):
self.counter = 0
def addition(self, num):
self.counter += num
return self
def multiply(self, num):
self.counter *= num
return self
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--add', required = False, action = 'store_true')
parser.add_argument('--multiply', required = False, action = 'store_true')
args = parser.parse_args()
maths = Maths()
maths.addition(10)
maths.multiply(5)
print(maths.counter)
if __name__ == "__main__":
main()
How can I accomplish ordering based on the order of how the arguments are passed in? Thank you!
This parser provides two ways of inputing lists of strings:
In [10]: parser = argparse.ArgumentParser()
...: parser.add_argument('--cmds', nargs='*', choices=['add','mult'])
...: parser.add_argument('--add', dest='actions', action='append_const', const='add')
...: parser.add_argument('--multiply', dest='actions', action = 'append_const', const='mult')
...: parser.print_help()
...:
...:
usage: ipython3 [-h] [--cmds [{add,mult} [{add,mult} ...]]] [--add]
[--multiply]
optional arguments:
-h, --help show this help message and exit
--cmds [{add,mult} [{add,mult} ...]]
--add
--multiply
As values of a '--cmds' argument:
In [11]: parser.parse_args('--cmds mult add'.split())
Out[11]: Namespace(actions=None, cmds=['mult', 'add'])
As separate flagged arguments:
In [12]: parser.parse_args('--mult --add'.split())
Out[12]: Namespace(actions=['mult', 'add'], cmds=None)
In both cases I create a list of strings. In the second the const values could be functions or methods.
const=maths.addition

Remove Carriage Return from the final print statement

import re
import sys
def isValid(s):
pattern_= re.compile("[12][\d]{12}$")
return pattern_.match(s)
loop = int(input ())
output=[]
for _ in range(0, loop):
ele = int(input())
output.append(ele)
entries = ''
for x in output :
entries += str(x)+ ''
print (output ) #['0123456789012']
print (entries ) #0123456789012
print(type(entries )) #str
print(type(output )) #list
# Driver Code
for _ in range(loop):
for x in entries:
if (isValid(x)):
sys.stdout.write ("Valid Number")
break
else :
sys.stdout.write ("Invalid Number")
break
Phones Numbers starts with the digit 1 or 2 followed by exactly 12 digits i.e Phones Numbers comprises of 13 digits.
For each Phone Number, print "Valid" or "Invalid" in a new line.
The list is taking wrong input
The output generated is,
2
0123456789012
1123456789012
[123456789012, 1123456789012]
123456789012 1123456789012
<class 'str'>
<class 'list'>
Invalid NumberInvalid Number
[Program finished]
Also, I have searched on stack before posting. This looked different issue. If anything matches the error on stack please redirect me there.
2
1123456789012
0123456778901
Valid Number
Invalid Number
[Program finished]
This is what it should look like
import re
def isValid(s):
pattern_= re.compile(r'[1|2][0-9]{12}$')
return pattern_.match(s)
loop = int(input())
# no of times loops to run
output = []
for _ in range(0, loop):
output.append(input())
entries = ''
for x in output :
entries += x + ''
result = []
# Driver Code
for val in output:
if isValid(val):
result.append('Valid Number')
else:
result.append ('Invalid Number')
for i in range(len(result )-1):
print(result[i])
print(result[-1], end = " ")
This should work too.
print first converts the object to a string (if it is not already a string). It will also put a space before the object if it is not the start of a line and a newline character at the end.
When using stdout, you need to convert the object to a string yourself (by calling "str", for example) and there is no newline character.
May I also suggest to rephrase your question as it's not a logic issue but a syntax issue.
Comment:
Checked with single and multiple inputs.
Works.
Try using the below regex
def is_valid(s):
pattern_= re.compile(r'[1|2][0-9]{12}$')
return pattern_.match(s)
I am not sure, why you are appending the numbers to the entities variable. I have changed the code a bit and the regex is working fine.
def is_valid(s):
pattern_= re.compile(r'[1|2][0-9]{12}$')
return pattern_.match(s)
loop = int(input())
output = []
for _ in range(0, loop):
output.append(input())
entries = ''
for x in output :
entries += x + ''
print (output ) # ['0123456789012']
print (entries ) # 0123456789012
print(type(entries )) # str
print(type(output )) # list
# Driver Code
for val in output:
if isValid(val):
print('Valid Number')
else:
print('Invalid Number')
Input:
5
1234567891234
1893456879354
2897347838389
0253478642678
6249842352985
Output:
['1234567891234', '1893456879354', '2897347838389', '0253478642678', '6249842352985']
12345678912341893456879354289734783838902534786426786249842352985
<class 'str'>
<class 'list'>
Valid Number
Valid Number
Valid Number
Invalid Number
Invalid Number
import sys
import re
def isValid(s):
pattern_= re.compile(r'[1|2][0-9]{12}$')
return pattern_.match(s)
loop = int(input())
output = []
for _ in range(0, loop):
output.append(input())
entries = ''
for x in output :
entries += x + ''
print (output ) # ['0123456789012']
print (entries ) # 0123456789012
print(type(entries )) # str
print(type(output )) # list
# Driver Code
for val in output:
if isValid(val):
sys.stdout.write('Valid Number')
else:
sys.stdout.write('Invalid Number')
produces
1
1234567891234
['1234567891234']
1234567891234
<class 'str'>
<class 'list'>
Valid Number
[Program finished]
print always returns carriage.
Whereas sys.stdout.write doesn't.
The challenge was resolved hence.

While calculating median I am getting run time error for all the test cases in python 3

# Enter your code here. Read input from STDIN. Print output to STDOUT
from statistics import median
a=str(input())
l=a.split(' ')
for i,v in enumerate(l):
l[i]=float(v)
print('%.9f'%median(l))
Try with some data validation to check the error,
# Enter your code here. Read input from STDIN. Print output to STDOUT
from statistics import median
a = str(input('Enter the series of values - '))
try:
l = [float(value.strip()) for value in a.split(' ') if value.strip()]
except Exception as error:
print('Error while float conversion - {}'.format(error))
l = []
if l:
print('%.9f' % median(l))
else:
print('Enter valid inputs.')

Executing mathematical user inputed statements as code in Python 3

How would one allow the user to input a statement such as "math.sin(x)**2" and calculate the answer within python code.
In telling me the answer please explain why both exec() compile() are not producing the desired result.
import math
def getValue(function, x):
function = "val = " + function
#compile(function, '', 'exec')
exec(function)
print(val)
function = input("Enter a function f(x):\n")
getValue(function, 10)
Much Appreciated!
To answer your question, use eval:
>>> eval('math.sin(1)**2')
0.7080734182735712
exec is working, but you are not retrieving the result. Notice:
>>> val
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'val' is not defined
>>> exec('val=math.sin(1)**2')
>>> val
0.7080734182735712
So, using eval instead of exec works like so:
def getValue(function, x):
function = "{}({})".format(function, x)
print(function)
val=eval(function)
print(val)
That said -- it is considered an extreme security risk to execute arbitrary user code.
If you are building a calculator, a safer approach you might consider using SymPy or building your own parser using something like PyParsing (which is used in SymPy)
An example PyParsing calculator:
import sys
import operator
from pyparsing import nums, oneOf, Word, Literal, Suppress
from pyparsing import ParseException, Forward, Group
op_map = { '*' : operator.mul,\
'+' : operator.add,\
'/' : operator.div,\
'-' : operator.sub}
exp = Forward()
number = Word(nums).setParseAction(lambda s, l, t: int(t[0]))
lparen = Literal('(').suppress()
rparen = Literal(')').suppress()
op = oneOf('+ - * /').setResultsName('op').setParseAction(lambda s, l, t: op_map[t[0]])
exp << Group(lparen + op + (number | exp) + (number | exp) + rparen)
def processArg(arg):
if isinstance(arg, int):
return arg
else:
return processList(arg)
def processList(lst):
args = [processArg(x) for x in lst[1:]]
return lst.op(args[0], args[1])
def handleLine(line):
result = exp.parseString(line)
return processList(result[0])
while True:
try:
print handleLine(raw_input('> '))
except ParseException, e:
print >>sys.stderr,\
"Syntax error at position %d: %s" % (e.col, e.line)
except ZeroDivisionError:
print >>sys.stderr,\
"Division by zero error"
Which can easily be extended to include other functions.

Resources