Trying to run my script using argparser, where the program does not run, unless correct argument is in place, however it does not seem to work;
AttributeError: 'Namespace' object has no attribute 'func'
import sys
import argparse
from develop import Autogit as gt
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
# Create argument command
parser_update = subparsers.add_parser('--sync', help='Sync local and remote repos')
parser_update.set_defaults(func=gt.run)
# Adding arguments
parser.add_argument('--sync', type=str, required=True)
if len(sys.argv) <= 1:
sys.argv.append('--help')
options = parser.parse_args()
options.func() # <--- Causes the error
if __name__ == '__main__':
main()
Also when the --sync arg is given it ask for another, then when I add one more argument. SYNC, then it returns attribute error.
Edit
Trying to make the program run the develop.Autogit.run
Working..
Had to also add args as argument in the run funciton i am calling.
I think what you are trying to accomplish is setting a default, typically this is done with ArgumentParser.set_defaults(). You need to do this with the uninitialised function. See this example:
import sys
import argparse
def f(args):
print("In func")
print(args)
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
# Create argument command
parser_update = subparsers.add_parser("sync", help="Sync local and remote repos")
parser_update.set_defaults(func=f) # <-- notice it's `f` not `f()`
options = parser.parse_args()
options.func(options)
if __name__ == "__main__":
main()
As an aside, you will have more problems with your snippet as you are defining the same parameter (--sync) in multiple places. When using subparsers it is customary to make these positional (no leading --) so they act as subcommands.
Here is a typical command line that I would use with subcommands:
import sys
import argparse
def f(args):
print("In func f")
print(args)
def g(args):
print("In func g")
print(args)
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command")
parser_update = subparsers.add_parser("sync", help="Sync local and remote repos")
parser_update.set_defaults(func=f)
parser_delete = subparsers.add_parser("delete", help="Delete sub-command")
parser_delete.set_defaults(func=g)
options = parser.parse_args()
if options.command is not None:
options.func(options)
else:
parser.print_help()
parser.exit()
if __name__ == "__main__":
main()
I have a script (A) that uses argparse to parse comand line args, I am writing script (B) that will generate various combinations of arguments for script A at runtime and call the main method of A.
I cannot modify script A.
Script A
import argparse
def main():
args = code_to_add_args().parse_args()
# does stuff with args
Script B
import argparse
from A import main
if __name__ == "__main__":
my_cli_args = code_to_add_args().parse_args()
new_args_generated_at_runtime = generate_args()
main() # pass new_args_generated_at_runtime into A to be parsed by argparse
How do I use script B to supply CLI args to script A without modifying script A?
The closest I have found is here, but its answer requires modifying the script A.
If possible I would prefer not to use subprocess.call()
The comment from hpaulj led me to a solution that worked.
I cleared sys.argv and then appended my args to it:
Script A
import argparse
def main():
args = code_to_add_args().parse_args()
# does stuff with args
Script B
import argparse
from A import main
if __name__ == "__main__":
my_cli_args = code_to_add_args().parse_args()
new_args_list = generate_list_of_args()
# Keep the first element of sys.argv to retain the name of the script
sys.argv = [sys.argv[0]]
for param in new_args_list:
sys.argv.append(param)
main() # Now new args have been added to sys.argv, argparse in script A will use them.
I have a script that I want to use with the python shell
(interactivity mode, live interpreter, python3 [return])
and the script I want to have added to the live interpreter (python3 -i script.py) has a if __name__ == '__main__': in it.
When I load the script the if __name__ runs.
I have argsprase in the if __name__ which spits out an error.
So,
I need to be able to add the script to the live interpreter,
but not have certain code in the script run, the code in if __name__.
script.py
#/usr/bin/python3
class myExcellentClass:
def __init__(var1, var2):
self.var1 = var1
self.var2 = var2
def WhatisVar1(self):
return self.var1
def WhatisVar2(self):
return self.var2
if __name__ == '__main__':
import argparse
# setup args parse
# do some stuff here
I'm thinking there must be a variable that I can add to if __name__ that will test for whether the script is being run with -i or not.
For example:
if __name__ == '__main__' && is_interactive == false:
import argparse
# setup args parse
# do some stuff here
If there is a way to call the live interpreter from in side a python3 script,
I would just add -i to the script and have this launch the class added to the live interpreter.
I could split out the class into another file.
I would like not to do this if possible.
Example:
scriptA.py
#/usr/bin/python3
class myExcellentClass:
def __init__(var1, var2):
self.var1 = var1
self.var2 = var2
def WhatisVar1(self):
return self.var1
def WhatisVar2(self):
return self.var2
scriptB.py
#/usr/bin/python3
from scriptA import *
if __name__ == '__main__' && is_interactive == false:
import argparse
# setup args parse
# do some stuff here
I usually install the script system wide as a byte-code file for efficiency purposes.
(Yes I know that it's not recommend, nor will the pyc work with different versions of python.)
As I only need to use the -i for testing and trouble shooting,
I would prepare a solution that would allow me to keep everything in one python file.
The variable you are looking for actually exists.
from sys import flags
print(flags.interactive)
This prints 1 in interactive mode and zero otherwise.
Just run python3 and the type from script import *.
a more complete answer here: What does if __name__ == "__main__": do?
I'm trying to use click to pass command-line arguments to a function but having difficulty. I'm trying to pass two command-line arguments with this:
python script.py --first-a hi --second-a there
Here's my attempt:
import click
#click.command()
#click.option("--first-a")
#click.option("--second-a")
def main(first_a, second_a):
print first_a, second_a
if __name__ == "__main__":
main(first_a, first_a)
This fails with:
NameError: name 'first_a' is not defined
I thought this had to do with dashes vs. underscores but removing the dashes and underscores (just using firsta and seconda) also fails with the same issue.
What am I doing incorrectly?
You need to call main() either without any arguments, or with a list of parameters as would normally be found in sys.argv.
Code:
if __name__ == "__main__":
main()
Test Code:
import click
#click.command()
#click.option("--first-a")
#click.option("--second-a")
def main(first_a, second_a):
print(first_a, second_a)
if __name__ == "__main__":
# test code
import sys
sys.argv[1:] = ['--first-a', 'hi', '--second-a', 'there']
# actual call to click command
main()
Results:
hi there
Say I have a module with the following:
def main():
pass
if __name__ == "__main__":
main()
I want to write a unit test for the bottom half (I'd like to achieve 100% coverage). I discovered the runpy builtin module that performs the import/__name__-setting mechanism, but I can't figure out how to mock or otherwise check that the main() function is called.
This is what I've tried so far:
import runpy
import mock
#mock.patch('foobar.main')
def test_main(self, main):
runpy.run_module('foobar', run_name='__main__')
main.assert_called_once_with()
I will choose another alternative which is to exclude the if __name__ == '__main__' from the coverage report , of course you can do that only if you already have a test case for your main() function in your tests.
As for why I choose to exclude rather than writing a new test case for the whole script is because if as I stated you already have a test case for your main() function the fact that you add an other test case for the script (just for having a 100 % coverage) will be just a duplicated one.
For how to exclude the if __name__ == '__main__' you can write a coverage configuration file and add in the section report:
[report]
exclude_lines =
if __name__ == .__main__.:
More info about the coverage configuration file can be found here.
Hope this can help.
You can do this using the imp module rather than the import statement. The problem with the import statement is that the test for '__main__' runs as part of the import statement before you get a chance to assign to runpy.__name__.
For example, you could use imp.load_source() like so:
import imp
runpy = imp.load_source('__main__', '/path/to/runpy.py')
The first parameter is assigned to __name__ of the imported module.
Whoa, I'm a little late to the party, but I recently ran into this issue and I think I came up with a better solution, so here it is...
I was working on a module that contained a dozen or so scripts all ending with this exact copypasta:
if __name__ == '__main__':
if '--help' in sys.argv or '-h' in sys.argv:
print(__doc__)
else:
sys.exit(main())
Not horrible, sure, but not testable either. My solution was to write a new function in one of my modules:
def run_script(name, doc, main):
"""Act like a script if we were invoked like a script."""
if name == '__main__':
if '--help' in sys.argv or '-h' in sys.argv:
sys.stdout.write(doc)
else:
sys.exit(main())
and then place this gem at the end of each script file:
run_script(__name__, __doc__, main)
Technically, this function will be run unconditionally whether your script was imported as a module or ran as a script. This is ok however because the function doesn't actually do anything unless the script is being ran as a script. So code coverage sees the function runs and says "yes, 100% code coverage!" Meanwhile, I wrote three tests to cover the function itself:
#patch('mymodule.utils.sys')
def test_run_script_as_import(self, sysMock):
"""The run_script() func is a NOP when name != __main__."""
mainMock = Mock()
sysMock.argv = []
run_script('some_module', 'docdocdoc', mainMock)
self.assertEqual(mainMock.mock_calls, [])
self.assertEqual(sysMock.exit.mock_calls, [])
self.assertEqual(sysMock.stdout.write.mock_calls, [])
#patch('mymodule.utils.sys')
def test_run_script_as_script(self, sysMock):
"""Invoke main() when run as a script."""
mainMock = Mock()
sysMock.argv = []
run_script('__main__', 'docdocdoc', mainMock)
mainMock.assert_called_once_with()
sysMock.exit.assert_called_once_with(mainMock())
self.assertEqual(sysMock.stdout.write.mock_calls, [])
#patch('mymodule.utils.sys')
def test_run_script_with_help(self, sysMock):
"""Print help when the user asks for help."""
mainMock = Mock()
for h in ('-h', '--help'):
sysMock.argv = [h]
run_script('__main__', h*5, mainMock)
self.assertEqual(mainMock.mock_calls, [])
self.assertEqual(sysMock.exit.mock_calls, [])
sysMock.stdout.write.assert_called_with(h*5)
Blam! Now you can write a testable main(), invoke it as a script, have 100% test coverage, and not need to ignore any code in your coverage report.
Python 3 solution:
import os
from importlib.machinery import SourceFileLoader
from importlib.util import spec_from_loader, module_from_spec
from importlib import reload
from unittest import TestCase
from unittest.mock import MagicMock, patch
class TestIfNameEqMain(TestCase):
def test_name_eq_main(self):
loader = SourceFileLoader('__main__',
os.path.join(os.path.dirname(os.path.dirname(__file__)),
'__main__.py'))
with self.assertRaises(SystemExit) as e:
loader.exec_module(module_from_spec(spec_from_loader(loader.name, loader)))
Using the alternative solution of defining your own little function:
# module.py
def main():
if __name__ == '__main__':
return 'sweet'
return 'child of mine'
You can test with:
# Override the `__name__` value in your module to '__main__'
with patch('module_name.__name__', '__main__'):
import module_name
self.assertEqual(module_name.main(), 'sweet')
with patch('module_name.__name__', 'anything else'):
reload(module_name)
del module_name
import module_name
self.assertEqual(module_name.main(), 'child of mine')
I did not want to exclude the lines in question, so based on this explanation of a solution, I implemented a simplified version of the alternate answer given here...
I wrapped if __name__ == "__main__": in a function to make it easily testable, and then called that function to retain logic:
# myapp.module.py
def main():
pass
def init():
if __name__ == "__main__":
main()
init()
I mocked the __name__ using unittest.mock to get at the lines in question:
from unittest.mock import patch, MagicMock
from myapp import module
def test_name_equals_main():
# Arrange
with patch.object(module, "main", MagicMock()) as mock_main:
with patch.object(module, "__name__", "__main__"):
# Act
module.init()
# Assert
mock_main.assert_called_once()
If you are sending arguments into the mocked function, like so,
if __name__ == "__main__":
main(main_args)
then you can use assert_called_once_with() for an even better test:
expected_args = ["expected_arg_1", "expected_arg_2"]
mock_main.assert_called_once_with(expected_args)
If desired, you can also add a return_value to the MagicMock() like so:
with patch.object(module, "main", MagicMock(return_value='foo')) as mock_main:
One approach is to run the modules as scripts (e.g. os.system(...)) and compare their stdout and stderr output to expected values.
I found this solution helpful. Works well if you use a function to keep all your script code.
The code will be handled as one code line. It doesn't matter if the entire line was executed for coverage counter (though this is not what you would actually actually expect by 100% coverage)
The trick is also accepted pylint. ;-)
if __name__ == '__main__': \
main()
If it's just to get the 100% and there is nothing "real" to test there, it is easier to ignore that line.
If you are using the regular coverage lib, you can just add a simple comment, and the line will be ignored in the coverage report.
if __name__ == '__main__':
main() # pragma: no cover
https://coverage.readthedocs.io/en/coverage-4.3.3/excluding.html
Another comment by # Taylor Edmiston also mentions it
My solution is to use imp.load_source() and force an exception to be raised early in main() by not providing a required CLI argument, providing a malformed argument, setting paths in such a way that a required file is not found, etc.
import imp
import os
import sys
def mainCond(testObj, srcFilePath, expectedExcType=SystemExit, cliArgsStr=''):
sys.argv = [os.path.basename(srcFilePath)] + (
[] if len(cliArgsStr) == 0 else cliArgsStr.split(' '))
testObj.assertRaises(expectedExcType, imp.load_source, '__main__', srcFilePath)
Then in your test class you can use this function like this:
def testMain(self):
mainCond(self, 'path/to/main.py', cliArgsStr='-d FailingArg')
To import your "main" code in pytest in order to test it you can import main module like other functions thanks to native importlib package :
def test_main():
import importlib
loader = importlib.machinery.SourceFileLoader("__main__", "src/glue_jobs/move_data_with_resource_partitionning.py")
runpy_main = loader.load_module()
assert runpy_main()