How to use argparse in the actual script - python-3.x

How does argparse work? I was told to 'hide' the password from the psycopg2 connection I am building so to be able to run the script automatically every week and being able to share it between departments. This is the beginning of the psycopg2 script where the password is asked:
#Connect to database
conn_string = "host='my_aws_postgresql_database.rds.amazonaws.com' dbname='my_database_name' user='my_username' password='my_password'"
# print the connection string we will use to connect
print ("\"Connecting to database\n ->%s\"" % (conn_string))
Now, how would I use argparse (and getpass) to hide my password? I found this script about this subject a couple of times (I would delete the print statement after getting it to work):
import argparse
import getpass
class Password(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
if values is None:
values = getpass.getpass()
setattr(namespace, self.dest, values)
parser = argparse.ArgumentParser('Test password parser')
parser.add_argument('-p', action=Password, nargs='?', dest='password',
help='Enter your password')
args = parser.parse_args()
print (args.password)
I tried to add the argparse snippet above the #Connect to database code. And replaced the password part on line 2 with
conn_string =
"host='my_aws_postgresql_database.rds.amazonaws.com'
dbname='my_database_name'
user='my_username'
password='" + args + "'"
Then I tried to run the whole script with the command python3 my_script_file.py my_password -p I get asked for the password which I entered, but this rendered the following error
usage: Test password parser [-h] [-p [PASSWORD]]
Test password parser: error: unrecognized arguments: my_password
If I use python3 my_script_file.py my_password I get the same error, but I did not have to enter the password (again).
Am I close to the solution? Is this the standard way of doing this?
The problem was that I used python3 my_script_file.py my_password -p instead of the correct order python3 my_script_file.py -p my_password, see accepted answer below by #hpaulj and the comments to that answer.

This parser is gives the user 2 ways of entering the password, on the commandline, or with a separate getpass prompt:
import argparse
import getpass
class Password(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
if values is None:
values = getpass.getpass()
setattr(namespace, self.dest, values)
parser = argparse.ArgumentParser('Test password parser')
parser.add_argument('-p', action=Password, nargs='?', dest='password',
help='Enter your password')
args = parser.parse_args()
print (args)
Sample runs:
0911:~/mypy$ python3 stack44571340.py
Namespace(password=None)
0912:~/mypy$ python3 stack44571340.py -p test
Namespace(password='test')
0912:~/mypy$ python3 stack44571340.py -p
Password:
Namespace(password='testing')
0912:~/mypy$ python3 stack44571340.py test
usage: Test password parser [-h] [-p [PASSWORD]]
Test password parser: error: unrecognized arguments: test
I tested without any arguments (got the default None)`; with '-p test' which uses the 'test' string; with just '-p', which asks; and without '-p', which produces the error.
I don't know why python3 my_script_file.py -p my_password produced an error; my best guess there's a typo in your parser definition (something wrong with nargs?).
It's not entirely clear how you merged this parser code into the larger script. Done right it shouldn't have changed the behavior of the parser.
The password argument would be used as:
password='" + args.password + "'"
The echo argument, is a positional one, which requires a string. In contrast the -p with nargs='?', is an optional flagged argument, which allows for the three way input I illustrated.
parser.add_argument("echo")

Thank you #CarlShiles, your answer didn't work with that long argparse/getpass snippet from above, but it made me realise that I could just echo the password in there. So I did a simple
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
and then used your suggestion, password='" + args.echo + "'". And then ran the following command python3 my_script_file.py my_password. This worked just fine.

Related

Is there a way to pass options values during mod_wsgi server start up in Django app

I am using mod_wsgi to run my Django app. As there are a plethora of options to define when the server run command is fired, I was trying to create some kind of python script to pass the options and their pre-set values.
For example:
Instead of using:
$python3 manage.py runmodwsgi --processes 3 --threads 1
I am trying to use a python script and use it as:
$python3 runme.py
where runme.py is the script file I am trying to use to start the server.
What I have done so far?
Created the script file:
import os
from django.core.management import execute_from_command_line
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<proj>.settings')
myargs = 'runmodwsgi'
list_of_args = ['', myargs]
execute_from_command_line(list_of_args)
As expected the server started with preset options, for example:
Now what I am trying to achieve is the pass values of certain options like:
--processes 3 --threads 1
and so on.
Is there a way I may pass the preset values (as I may be able to define in my script file runme.py), say something like adding to the list of arguments:
list_of_args = ['', myargs, addl_args]
I have been checking SO queries posted in addition to help available on python site, but could not get my head around to the problem.
I tried the following which is not very helpful though:
import os
import argparse # New import
from django.core.management import execute_from_command_line
# Addition of new code lines
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-p", "--processes", action="store_true")
group.add_argument("-t", "--threads", action="store_true")
parser.add_argument("pint", type=int, help="no. of processes")
parser.add_argument("tint", type=int, help="no. of threads")
args = parser.parse_args()
# End of new code
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<proj>.settings')
myargs = 'runmodwsgi'
# At this line if I add ", args" (like shown below:])
list_of_args = ['', myargs, args]
execute_from_command_line(list_of_args)
and run the command, it comes up with error:
TypeError: 'Namespace' object does not support indexing
If I simply run: python3 runme.py, I get the following error:
usage: runme.py [-h] [-p | -t] pint tint
runme.py: error: the following arguments are required: pint, tint
Whereas, using
python3 runme.py 3 1
starts the server but the options integers "3" and "1" does not have any effect (as intended for no. of processes and threads).
If I use:
python3 runme.py --processes 3 --threads 1
I get the following error:
usage: runme.py [-h] [-p | -t] pint tint
runme.py: error: argument -t/--threads: not allowed with argument -p/--processes
Tried with a single arg like:
python3 runme.py --processes 3 1
The server starts at this time but the options value/s are not affected.
How do I define the option values and pass these preset values of options to the run command?
It looks like you're just misunderstanding/misusing argparse.
import os
import sys
import argparse
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<proj>.settings')
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--processes", type=int, default=1)
parser.add_argument("-t", "--threads", type=int, default=1)
args = parser.parse_args()
from django.core.management import execute_from_command_line
command = [sys.argv[0], 'runmodwsgi', '--processes', str(args.processes), '--threads', str(args.threads)]
execute_from_command_line(command)
might be closer to what you want; it will default processes and threads both to 1.

Python3 argparse nargs="+" get number of arguments

I'm now googling for quite a while and I just don't find any solution to my problem.
I am using argparse to parse some command line arguments. I want to be able to parse an arbitrary number of arguments > 1 (therefore nargs="+") and I want to know afterwards how many arguments I have parsed. The arguments are all strings. But I get a problem when I just have one argument, because then the length of the list is the number of characters of the word and not 1 as in 1 argument. And I want to know how many arguments were parsed. Does anyone know how I could solve this problem?
examples:
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", type=str, nargs="+", required=False, default="hi")
args = parser.parse_args()
test = args.test
print(test)
print(len(test))
So with python3 example.py --test hello hallo hey the output is:
['hello', 'hallo', 'hey']
3
But with python3 example.py --test servus the output is:
servus
6
What I already know is that I could do print([test]) but I only want to do that if I have 1 argument. Because if I have more than one arguments and use print([test]) I get a double array... So I just want to know the number of parsed arguments for "test".
I cannot imagine that I am the only one with such a problem, but I could not find anything in the internet. Is there a quick and clean solution?
You left off the test=args.test line.
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", type=str, nargs="+", required=False, default="hi")
args = parser.parse_args()
print(args)
print(len(args.test))
test cases
0856:~/mypy$ python3 stack68020846.py --test one two three
Namespace(test=['one', 'two', 'three'])
3
0856:~/mypy$ python3 stack68020846.py --test one
Namespace(test=['one'])
1
0856:~/mypy$ python3 stack68020846.py
Namespace(test='hi')
2
change the default to default=[]
0856:~/mypy$ python3 stack68020846.py
Namespace(test=[])
0
Theres probably a better solution, but try this:
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", type=str, nargs="+", required=False, default="hi")
args = parser.parse_args()
print("Number of arguments:", len(args._get_kwargs()[-1][-1]))
By the way, i figured this out by checking the content of args with print(dir(args))

How do I wrap a call to sudo in Python3 and capture any output and errors?

Because of the changes in bytes vs string handling in Python3 the older solutions to similar questions are hard to use.
So I'm posting this just to provide a working example.
The generic answer is still, use the subprocess.Popen functionality with subprocess.PIPE objects, and use the sudo -S (--stdin) command line, to sanely feed the password into the running sudo command without doing psuedo-terminal handling.
The wrinkle is that the password has to be encoded as Python bytes and any captured output and error messages must be encoded back into Python strings.
So, without further ado, here's one working solution:
#!python
#!/usr/bin/env python
import sys
from getpass import getpass
from subprocess import Popen, PIPE
from shlex import split as shplit
if __name__ == '__main__':
cmd = 'sudo -S id'
response = getpass()
pw = bytes(response+'\r\n', 'ascii')
proc = Popen(shplit(cmd), stdin=PIPE, stdout=PIPE, stderr=PIPE)
out = err = None # In case of timeout
out, err = proc.communicate(input=pw, timeout=None)
out = str(out,'ascii')
err = str(err,'ascii')
if err.startswith('Password:'):
err = err[len('Password:'):]
if err:
print('Errors: ', err)
print()
if out:
print('Output: \n', out)
results = proc.poll()
errorcode = proc.returncode
print('Return code: ', errorcode)
sys.exit(errorcode)
Obviously most of this is in the wrapping. Most real code would get getting the password and the command for sudo to execute via some other means; and the post-processing of the output and error messages would normally be different. (Actually this example does a poor job of filtering sudo's prompt for your password out of the final output; fixing that's left as an exercise to the reader).
Of course this uses shlex.split as the safer approach than shell=True.

argparse with multiple flags and arguments

I want my code to be able to call different functions according to the flag and then use the argument passed after the flag as the input to the function.
Example of the expected output:
$ python3 test.py -a 2
4
$ python3 test.py -a human123
<Error message>
$ python3 test.py -h human123
Hello, human123
Here's my sample code:
class Test:
def __init__(self):
pass
def add(self, a):
return a+a
def hello(self, name):
return f"Hello, {name}"
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--add', dest='command', action='store_consts', const='add', nargs=1, help='add a to itself')
parser.add_argument('-h', '--hello', dest='command', action='store_consts', const='hello', nargs=1, help='hello!')
args = parser.parse_args()
t = Test()
if args.command=='add':
print(t.add(args.add))
elif args.command=='sub':
print(t.hello(args.hello))
This sample code currently is doing what I want to achieve. I tried many things to fix the issue from removing the 'consts', changing the action to 'store', changing the value of nargs to '?', etc., however, it keeps giving me different kinds of errors like TypeError, etc.
Simplifying your arguments:
import argparse
class Test:
def __init__(self):
pass
def add(self, a):
return a+a
def hello(self, name):
return f"Hello, {name}"
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--add', help='add a to itself')
parser.add_argument('-b', '--hello', help='hello!') # -h is already taken
args = parser.parse_args()
print(args)
t = Test()
if args.add: # or 'args.add is not None:'
print(t.add(args.add))
elif args.hello:
print(t.hello(args.hello))
test runs:
1936:~/mypy$ python3 stack62702524.py -a testing
Namespace(add='testing', hello=None)
testingtesting
1937:~/mypy$ python3 stack62702524.py -b other
Namespace(add=None, hello='other')
Hello, other
1937:~/mypy$ python3 stack62702524.py
Namespace(add=None, hello=None)
===
Your errors, which you did not show :{ When you get errors, don't just throw up your hands and randomly try alternatives. Read the docs and try to understand the error.
parser.add_argument('-c', action='store_consts', const='hello')
ValueError: unknown action "store_consts"
parser.add_argument('-c', action='store_const', const='hello', nargs=1)
TypeError: __init__() got an unexpected keyword argument 'nargs'
'store_consts' is an name error; with 'store_const' nargs is fixed at 0; you can't change that.
If I add 3 arguments - two store_const and one positional:
parser.add_argument('-c', dest='command', action='store_const', const='add')
parser.add_argument('-d', dest='command', action='store_const', const='hello')
parser.add_argument('foo')
Note the two new command and foo attributes, which you could use in your function call:
1945:~/mypy$ python3 stack62702524.py -c bar
Namespace(add=None, command='add', foo='bar', hello=None)
1945:~/mypy$ python3 stack62702524.py -d bar
Namespace(add=None, command='hello', foo='bar', hello=None)
Typically I use the dest parameter of add_argument to specify the variable name.
For example:
parser.add_argument("-a", "--add", dest="add", required=False, type=str help="some help")
Could be accessed by:
args = parser.parse_args()
print(args.add == "something")
I believe that you need one unique dest per argument.
Also, -h is reserved for help. You may wish to change this to -e or something.
parser.add_argument('-h', '--hello', ...)

I am trying to check if system password entered is correct or not using bash script, using subprocess()

I am making a program where user enters system password and I check it.
I am using subprocess() to pass password to a random install command in bash and trying to get grab output using check_output and check that against some value but unable to do so.
Here is what I have tried
import os
import subprocess
def mainFunction(password):
commandToRunRouter="echo " +password + " | sudo -S install something"
answer=subprocess.check_output(commandToRunRouter,shell=True)
print("result")
print(answer)
if answer!=0:
return False
return True
Answer variable should store value 'Sorry wrong password.' which is what is displayed when you enter wrong password but it is storing some random variables.
What am I doing wrong??
Checkout the documentation for check_output:
If the return code was non-zero it raises a CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute and any output in the output attribute.
That means you have to catch the exception and readout the output from there. Also make sure to capture stdout and stderr. Something like this might work for you:
import os
import subprocess
def mainFunction(password):
commandToRunRouter="echo " +password + " | sudo -S install something"
try:
answer=subprocess.check_output(commandToRunRouter,shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as cpe:
print(cpe.returncode)
print(cpe.output)
return False
return True
Also consider not passing the password over a pipe as that might be a security issue.
Thanks for reply but i found one more way to do that using subporcess.call(), I am posting my answer below for use of others-
#!/usr/bin/env python3
import os
import subprocess
def start(password):
commandToRunRouter="echo " +password + " | sudo -S apt update"
answer=subprocess.call(commandToRunRouter,shell=True)
if answer==0:
return True
else:
return False

Resources