I wrote a simple command line utility that accepts a text file and searches for a given word in it using the click module.
sfind.py
import click
#click.command()
#click.option('--name', prompt='Word or string')
#click.option('--filename', default='file.txt', prompt='file name')
#click.option('--param', default=1, prompt="Use 1 for save line and 2 for word, default: ")
def find(name, filename, param):
"""Simple program that find word or string at text file and put it in new"""
try:
with open(filename) as f, open('result.txt', 'w') as f2:
count = 0
for line in f:
if name in line:
if param == 1:
f2.write(line + '\n')
elif param == 2:
f2.write(name + '\n')
count += 1
print("Find: {} sample".format(count))
return count
except FileNotFoundError:
print('WARNING! ' + 'File: ' + filename + ' not found')
if __name__ == '__main__':
find()
Now I need to write a test using unittest (using unittest is required).
test_sfind.py
import unittest
import sfind
class SfindTest(unittest.TestCase):
def test_sfind(self):
self.assertEqual(sfind.find(), 4)
if __name__ == '__main__' :
unittest.main()
When I run the test:
python -m unittest test_sfind.py
I get an error
click.exceptions.UsageError: Got unexpected extra argument (test_sfind.py)
How can I test this click command?
You can not simply call a click command and then expect it to return. The decorators applied to make a click command considerably change the behavior of the function. Fortunately the click frameworks provides for this through the CliRunner class.
Your command can be tested via unittest with something like this:
import unittest
import sfind
from click.testing import CliRunner
class TestSfind(unittest.TestCase):
def test_sfind(self):
runner = CliRunner()
result = runner.invoke(
sfind.find, '--name url --filename good'.split(), input='2')
self.assertEqual(0, result.exit_code)
self.assertIn('Find: 3 sample', result.output)
For those wanting to test exceptions in a click command, I have found this way to do it:
def test_download_artifacts(
self,
):
runner = CliRunner()
# test exception raised for invalid dir format
result = runner.invoke(
my_module.download_artifacts,
'--bucket_name my_bucket \
--artifact_dir artifact_dir'.split(),
input='2')
print(f"result.exception: {result.exception}")
assert "Enter artifact_dir ending" in str(result.exception)
I have a python script which is controlled by a config file called system.config .the structure of config file is like bellow with some default values.
[company]
companyname: XYZ
[profile]
name: ABC
joining: 1/1/2014
the code for config file is : config_parser_details.py
import ConfigParser
import sys
Config = ConfigParser.ConfigParser()
Config.read("system.config")
filename = "system.config"
def ConfigSectionMap(section):
dict1 = {}
options = Config.options(section)
for option in options:
try:
dict1[option] = Config.get(section, option)
if dict1[option] == -1:
DebugPrint("skip: %s" % option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
company = ConfigSectionMap("company")['companyname']
name = ConfigSectionMap("profile")['name']
joindate = ConfigSectionMap("profile")['joining']
now the code for my script is : test.py
import config_parser_details as p
import sys
import warnings
import os
company = p.company
name = p.name
date = p.joindate
print("%s\n" %company)
print("%s\n" %name)
output is
XYZ
ABC
now I want to give input in the config file through command line.
like
python test.py --compname ="testing"
if any argument is missing in the command line than default value will be the input.
You could use argparse library to parse command line arguments.
So your test.py file looks like below :
import config_parser_details as p
import sys
import warnings
import os
import argparse
commandLineArgumentParser = argparse.ArgumentParser()
commandLineArgumentParser.add_argument("-c", "--compname", help="Company name", default=p.company)
commandLineArguments = commandLineArgumentParser.parse_args()
company = commandLineArguments.compname
name = p.name
date = p.joindate
print("%s\n" %company)
print("%s\n" %name)
I'd advise looking into a tool like docopt.
For a quick fix though, you can try doing this
def ConfigSectionMap(section):
options = Config.options(section)
arg_dict = {}
for command_line_argument in sys.argv[1:]:
arg = command_line_argument.split("=")
arg_dict[arg[0][2:]] = arg[1]
for key in arg_dict:
options[key] = arg_dict[key]
return options
This will load up all the default option. Any options put on the command line will override or add to the options dict.
First of all, I'd move code into a main section so that you can import config_parser_details without executing code:
if __name__ == '__main__':
main()
def main():
Config = ConfigParser.ConfigParser()
Config.read("system.config")
filename = "system.config"
company = ConfigSectionMap("company")['companyname']
name = ConfigSectionMap("profile")['name']
joindate = ConfigSectionMap("profile")['joining']
Secondly, I'd use STB land's suggestion of parsing the command line with argparse, something like:
def main():
# do the parsing thing first, then:
filename = args.filename
do_stuff(filename)
This way you can neatly use python's own unit test framework or nosetests to write test file that don't require you to manually specify parameters:
def test_basic():
# create a temporary file with tempfile.NamedTemporaryFile
tmpfile = tempfile.NamedTemporaryFile()
# add test data to tmpfile
do_stuff(tmpfile)
# check the output
assert ....
This comes with the added benefit of not having global variables, which will complicate your life later.
This question already has answers here:
How do I mock the filesystem in Python unit tests?
(5 answers)
Closed 1 year ago.
I have a source code that opens a csv file and sets up a header to
value association. The source code is given below:
def ParseCsvFile(source):
"""Parse the csv file.
Args:
source: file to be parsed
Returns: the list of dictionary entities; each dictionary contains
attribute to value mapping or its equivalent.
"""
global rack_file
rack_type_file = None
try:
rack_file = source
rack_type_file = open(rack_file) # Need to mock this line.
headers = rack_type_file.readline().split(',')
length = len(headers)
reader = csv.reader(rack_type_file, delimiter=',')
attributes_list=[] # list of dictionaries.
for line in reader:
# More process to happeng. Converting the rack name to sequence.
attributes_list.append(dict((headers[i],
line[i]) for i in range(length)))
return attributes_list
except IOError, (errno, strerror):
logging.error("I/O error(%s): %s" % (errno, strerror))
except IndexError, (errno, strerror):
logging.error('Index Error(%s), %s' %(errno, strerror))
finally:
rack_type_file.close()
I am trying to mock the following statement
rack_type_file = open(rack_file)
How do I mock open(...) function?
This is admittedly an old question, hence some of the answers are outdated.
In the current version of the mock library there is a convenience function designed for precisely this purpose. Here's how it works:
>>> from mock import mock_open
>>> m = mock_open()
>>> with patch('__main__.open', m, create=True):
... with open('foo', 'w') as h:
... h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
call().__enter__(),
call().write('some stuff'),
call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
Documentation is here.
To mock built-in function open with mox use __builtin__ module:
import __builtin__ # unlike __builtins__ this must be imported
m = mox.Mox()
m.StubOutWithMock(__builtin__, 'open')
open('ftphelp.yml', 'rb').AndReturn(StringIO("fake file content"))
m.ReplayAll()
# call the code you want to test that calls `open`
m.VerifyAll()
m.UnsetStubs()
Note that __builtins__ is not always a module, it can be of type dict, please use __builtin__ (with no "s") module to refer to system built-in methods.
More about __builtin__ module: http://docs.python.org/library/builtin.html
There are two ways that I like to do this, depending on the situation.
If your unit test is going to call ParseCsvFile directly I would add a new kwarg to ParseCsvFile:
def ParseCsvFile(source, open=open):
# ...
rack_type_file = open(rack_file) # Need to mock this line.
Then your unit test can pass a different open_func in order to accomplish the mocking.
If your unit test calls some other function that in turn calls ParseCsvFile then passing around open_func just for tests is ugly. In that case I would use the mock module. This lets you alter a function by name and replace it with a Mock object.
# code.py
def open_func(name):
return open(name)
def ParseCsvFile(source):
# ...
rack_type_file = open_func(rack_file) # Need to mock this line.
# test.py
import unittest
import mock
from StringIO import StringIO
#mock.patch('code.open_func')
class ParseCsvTest(unittest.TestCase):
def test_parse(self, open_mock):
open_mock.return_value = StringIO("my,example,input")
# ...
Is simple with decorator (Python3):
def my_method():
with open(file="/1.txt", mode='r', encoding='utf-8') as file:
return file.read().strip()
#mock.patch("builtins.open", create=True)
def test_my_method(mock_open):
mock_open.side_effect = [
mock.mock_open(read_data="A").return_value
]
resA = my_method()
assert resA == "A"
mock_open.mock_calls == [mock.call(file="/1.txt", mode='r', encoding='utf-8')]
I took the liberty of re-writing your sample function:
Assume your function is located in a file named code.py
# code.py
import csv
import logging
def ParseCsvFile(source):
"""Parse the csv file.
Args:
source: file to be parsed
Returns: the list of dictionary entities; each dictionary contains
attribute to value mapping or its equivalent.
"""
global rack_file
rack_file = source
attributes_list = []
try:
rack_type_file = open(rack_file)
except IOError, (errno, strerror):
logging.error("I/O error(%s): %s", errno, strerror)
else:
reader = csv.DictReader(rack_type_file, delimiter=',')
attributes_list = [line for line in reader] # list of dictionaries
rack_type_file.close()
return attributes_list
A simple test case would be:
# your test file
import __builtin__
import unittest
import contextlib
from StringIO import StringIO
import mox
import code
#contextlib.contextmanager
def mox_replayer(mox_instance):
mox_instance.ReplayAll()
yield
mox_instance.VerifyAll()
class TestParseCSVFile(unittest.TestCase):
def setUp(self):
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
def test_parse_csv_file_returns_list_of_dicts(self):
TEST_FILE_NAME = 'foo.csv'
self.mox.StubOutWithMock(__builtin__, 'open')
open(TEST_FILE_NAME).AndReturn(StringIO("name,age\nfoo,13"))
with mox_replayer(self.mox):
result = code.ParseCsvFile(TEST_FILE_NAME)
self.assertEqual(result, [{'age': '13', 'name': 'foo'}]) # works!
if __name__ == '__main__':
unittest.main()
EDIT:
% /usr/bin/python2.6
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import __builtin__
>>> import mox
>>> mock = mox.Mox()
>>> mock.StubOutWithMock(__builtin__, 'open')
>>> mock.UnsetStubs()
Works fine on 2.6 using mox 0.53
Hi I was having a similar problem, and was tearing my hair out flipping between different mocking libraries. I finally found a solution that I am happy with, and maybe it might help you? In the end I went with the Mocker library http://labix.org/mocker and here is the code for mocking open:
from mocker import Mocker
from StringIO import StringIO
import __builtin__
mocker = Mocker()
sourceFile = 'myTestFile.txt'
__builtin__.open = mocker.mock()
__builtin__.open(sourceFile)
mocker.result(StringIO('this,is,a,test,file'))
<the rest of your test setup goes here>
mocker.replay()
ParseCsvFile(sourceFile)
mocker.restore()
mocker.verify()
Incidentaly the reason I went with Mocker is because I was testing a function which used open to read a file, and then used open again to overwrite the same file with new data. What I needed to be able to do was test the case where the initial file didn't exist, so set up a mock, that threw an IOError the first time, and then worked the second time. The setup for which looked like this:
from mocker import Mocker
import __builtin__
mocker = Mocker()
mockFileObject = mocker.mock()
__builtin__.open = mocker.mock()
__builtin__.open('previousState.pkl', 'r')
mocker.throw(IOError('Boom'))
__builtin__.open('previousState.pkl','w')
mocker.result(mockFileObject)
<rest of test setup >
mocker.replay()
<test>
mocker.restore() #required to restore the open method
mocker.verify()
Hope this helps!
#mock.patch decorator (2.7 example)
This is now much easier:
import your_script.py
import __builtin__
import mock
#mock.patch("__builtin__.open")
def test_example(self, mock_open):
your_script.your_method()
self.assertEqual(mock_open.call_count, 1)
>>> class A(object):
... def __init__(self):
... self.x = open('test.py')
...
>>> old_open = open
>>> def open(s):
... return "test\n"
...
>>> a = A()
>>> a.x
'test\n'
>>> open = old_open
>>> a = A()
>>> a.x
<open file 'test.py', mode 'r' at 0xb7736230>
I am writing a program that categorizes a list of Python files by which modules they import. As such I need to scan the collection of .py files ad return a list of which modules they import. As an example, if one of the files I import has the following lines:
import os
import sys, gtk
I would like it to return:
["os", "sys", "gtk"]
I played with modulefinder and wrote:
from modulefinder import ModuleFinder
finder = ModuleFinder()
finder.run_script('testscript.py')
print 'Loaded modules:'
for name, mod in finder.modules.iteritems():
print '%s ' % name,
but this returns more than just the modules used in the script. As an example in a script which merely has:
import os
print os.getenv('USERNAME')
The modules returned from the ModuleFinder script return:
tokenize heapq __future__ copy_reg sre_compile _collections cStringIO _sre functools random cPickle __builtin__ subprocess cmd gc __main__ operator array select _heapq _threading_local abc _bisect posixpath _random os2emxpath tempfile errno pprint binascii token sre_constants re _abcoll collections ntpath threading opcode _struct _warnings math shlex fcntl genericpath stat string warnings UserDict inspect repr struct sys pwd imp getopt readline copy bdb types strop _functools keyword thread StringIO bisect pickle signal traceback difflib marshal linecache itertools dummy_thread posix doctest unittest time sre_parse os pdb dis
...whereas I just want it to return 'os', as that was the module used in the script.
Can anyone help me achieve this?
UPDATE: I just want to clarify that I would like to do this without running the Python file being analyzed, and just scanning the code.
IMO the best way todo this is to use the http://furius.ca/snakefood/ package. The author has done all of the required work to get not only directly imported modules but it uses the AST to parse the code for runtime dependencies that a more static analysis would miss.
Worked up a command example to demonstrate:
sfood ./example.py | sfood-cluster > example.deps
That will generate a basic dependency file of each unique module. For even more detail use:
sfood -r -i ./example.py | sfood-cluster > example.deps
To walk a tree and find all imports, you can also do this in code:
Please NOTE - The AST chunks of this routine were lifted from the snakefood source which has this copyright: Copyright (C) 2001-2007 Martin Blais. All Rights Reserved.
import os
import compiler
from compiler.ast import Discard, Const
from compiler.visitor import ASTVisitor
def pyfiles(startPath):
r = []
d = os.path.abspath(startPath)
if os.path.exists(d) and os.path.isdir(d):
for root, dirs, files in os.walk(d):
for f in files:
n, ext = os.path.splitext(f)
if ext == '.py':
r.append([d, f])
return r
class ImportVisitor(object):
def __init__(self):
self.modules = []
self.recent = []
def visitImport(self, node):
self.accept_imports()
self.recent.extend((x[0], None, x[1] or x[0], node.lineno, 0)
for x in node.names)
def visitFrom(self, node):
self.accept_imports()
modname = node.modname
if modname == '__future__':
return # Ignore these.
for name, as_ in node.names:
if name == '*':
# We really don't know...
mod = (modname, None, None, node.lineno, node.level)
else:
mod = (modname, name, as_ or name, node.lineno, node.level)
self.recent.append(mod)
def default(self, node):
pragma = None
if self.recent:
if isinstance(node, Discard):
children = node.getChildren()
if len(children) == 1 and isinstance(children[0], Const):
const_node = children[0]
pragma = const_node.value
self.accept_imports(pragma)
def accept_imports(self, pragma=None):
self.modules.extend((m, r, l, n, lvl, pragma)
for (m, r, l, n, lvl) in self.recent)
self.recent = []
def finalize(self):
self.accept_imports()
return self.modules
class ImportWalker(ASTVisitor):
def __init__(self, visitor):
ASTVisitor.__init__(self)
self._visitor = visitor
def default(self, node, *args):
self._visitor.default(node)
ASTVisitor.default(self, node, *args)
def parse_python_source(fn):
contents = open(fn, 'rU').read()
ast = compiler.parse(contents)
vis = ImportVisitor()
compiler.walk(ast, vis, ImportWalker(vis))
return vis.finalize()
for d, f in pyfiles('/Users/bear/temp/foobar'):
print d, f
print parse_python_source(os.path.join(d, f))
I recently needed all the dependencies for a given python script and I took a different approach than the other answers. I only cared about top level module module names (eg, I wanted foo from import foo.bar).
This is the code using the ast module:
import ast
modules = set()
def visit_Import(node):
for name in node.names:
modules.add(name.name.split(".")[0])
def visit_ImportFrom(node):
# if node.module is missing it's a "from . import ..." statement
# if level > 0 it's a "from .submodule import ..." statement
if node.module is not None and node.level == 0:
modules.add(node.module.split(".")[0])
node_iter = ast.NodeVisitor()
node_iter.visit_Import = visit_Import
node_iter.visit_ImportFrom = visit_ImportFrom
Testing with a python file foo.py that contains:
# foo.py
import sys, os
import foo1
from foo2 import bar
from foo3 import bar as che
import foo4 as boo
import foo5.zoo
from foo6 import *
from . import foo7, foo8
from .foo12 import foo13
from foo9 import foo10, foo11
def do():
import bar1
from bar2 import foo
from bar3 import che as baz
I could get all the modules in foo.py by doing something like this:
with open("foo.py") as f:
node_iter.visit(ast.parse(f.read()))
print(modules)
which would give me this output:
set(['bar1', 'bar3', 'bar2', 'sys', 'foo9', 'foo4', 'foo5', 'foo6', 'os', 'foo1', 'foo2', 'foo3'])
You might want to try dis (pun intended):
import dis
from collections import defaultdict
from pprint import pprint
statements = """
from __future__ import (absolute_import,
division)
import os
import collections, itertools
from math import *
from gzip import open as gzip_open
from subprocess import check_output, Popen
"""
instructions = dis.get_instructions(statements)
imports = [__ for __ in instructions if 'IMPORT' in __.opname]
grouped = defaultdict(list)
for instr in imports:
grouped[instr.opname].append(instr.argval)
pprint(grouped)
outputs
defaultdict(<class 'list'>,
{'IMPORT_FROM': ['absolute_import',
'division',
'open',
'check_output',
'Popen'],
'IMPORT_NAME': ['__future__',
'os',
'collections',
'itertools',
'math',
'gzip',
'subprocess'],
'IMPORT_STAR': [None]})
Your imported modules are grouped['IMPORT_NAME'].
It depends how thorough you want to be. Used modules is a turing complete problem: some python code uses lazy importing to only import things they actually use on a particular run, some generate things to import dynamically (e.g. plugin systems).
python -v will trace import statements - its arguably the simplest thing to check.
This works - using importlib to actually import the module, and inspect to get the members :
#! /usr/bin/env python
#
# test.py
#
# Find Modules
#
import inspect, importlib as implib
if __name__ == "__main__":
mod = implib.import_module( "example" )
for i in inspect.getmembers(mod, inspect.ismodule ):
print i[0]
#! /usr/bin/env python
#
# example.py
#
import sys
from os import path
if __name__ == "__main__":
print "Hello World !!!!"
Output :
tony#laptop .../~:$ ./test.py
path
sys
I was looking for something similar and I found a gem in a package called PyScons. The Scanner does just what you want (in 7 lines), using an import_hook. Here is an abbreviated example:
import modulefinder, sys
class SingleFileModuleFinder(modulefinder.ModuleFinder):
def import_hook(self, name, caller, *arg, **kwarg):
if caller.__file__ == self.name:
# Only call the parent at the top level.
return modulefinder.ModuleFinder.import_hook(self, name, caller, *arg, **kwarg)
def __call__(self, node):
self.name = str(node)
self.run_script(self.name)
if __name__ == '__main__':
# Example entry, run with './script.py filename'
print 'looking for includes in %s' % sys.argv[1]
mf = SingleFileModuleFinder()
mf(sys.argv[1])
print '\n'.join(mf.modules.keys())
Well, you could always write a simple script that searches the file for import statements. This one finds all imported modules and files, including those imported in functions or classes:
def find_imports(toCheck):
"""
Given a filename, returns a list of modules imported by the program.
Only modules that can be imported from the current directory
will be included. This program does not run the code, so import statements
in if/else or try/except blocks will always be included.
"""
import imp
importedItems = []
with open(toCheck, 'r') as pyFile:
for line in pyFile:
# ignore comments
line = line.strip().partition("#")[0].partition("as")[0].split(' ')
if line[0] == "import":
for imported in line[1:]:
# remove commas (this doesn't check for commas if
# they're supposed to be there!
imported = imported.strip(", ")
try:
# check to see if the module can be imported
# (doesn't actually import - just finds it if it exists)
imp.find_module(imported)
# add to the list of items we imported
importedItems.append(imported)
except ImportError:
# ignore items that can't be imported
# (unless that isn't what you want?)
pass
return importedItems
toCheck = raw_input("Which file should be checked: ")
print find_imports(toCheck)
This doesn't do anything for from module import something style imports, though that could easily be added, depending on how you want to deal with those. It also doesn't do any syntax checking, so if you have some funny business like import sys gtk, os it will think you've imported all three modules even though the line is an error. It also doesn't deal with try/except type statements with regards to import - if it could be imported, this function will list it. It also doesn't deal well with multiple imports per line if you use the as keyword. The real issue here is that I'd have to write a full parser to really do this correctly. The given code works in many cases, as long as you understand there are definite corner cases.
One issue is that relative imports will fail if this script isn't in the same directory as the given file. You may want to add the directory of the given script to sys.path.
I know this is old but I was also looking for such a solution like OP did.
So I wrote this code to find imported modules by scripts in a folder.
It works with import abc and from abc import cde format. I hope it helps someone else.
import re
import os
def get_imported_modules(folder):
files = [f for f in os.listdir(folder) if f.endswith(".py")]
imports = []
for file in files:
with open(os.path.join(folder, file), mode="r") as f:
lines = f.read()
result = re.findall(r"(?<!from)import (\w+)[\n.]|from\s+(\w+)\s+import", lines)
for imp in result:
for i in imp:
if len(i):
if i not in imports:
imports.append(i)
return imports
Thanks Tony Suffolk for inspect, importlib samples ... I built this wee module and you're all welcome to use it if it helps you. Giving back, yaaaay!
import timeit
import os
import inspect, importlib as implib
import textwrap as twrap
def src_modules(filename):
assert (len(filename)>1)
mod = implib.import_module(filename.split(".")[0])
ml_alias = []
ml_actual = []
ml_together = []
ml_final = []
for i in inspect.getmembers(mod, inspect.ismodule):
ml_alias.append(i[0])
ml_actual.append((str(i[1]).split(" ")[1]))
ml_together = zip(ml_actual, ml_alias)
for t in ml_together:
(a,b) = t
ml_final.append(a+":="+b)
return ml_final
def l_to_str(itr):
assert(len(itr)>0)
itr.sort()
r_str = ""
for i in itr:
r_str += i+" "
return r_str
def src_info(filename, start_time=timeit.default_timer()):
assert (len(filename)>1)
filename_in = filename
filename = filename_in.split(".")[0]
if __name__ == filename:
output_module = filename
else:
output_module = __name__
print ("\n" + (80 * "#"))
print (" runtime ~= {0} ms".format(round(((timeit.default_timer() - start_time)*1000),3)))
print (" source file --> '{0}'".format(filename_in))
print (" output via --> '{0}'".format(output_module))
print (" modules used in '{0}':".format(filename))
print (" "+"\n ".join(twrap.wrap(l_to_str(src_modules(filename)), 75)))
print (80 * "#")
return ""
if __name__ == "__main__":
src_info(os.path.basename(__file__))
## how to use in X file:
#
# import print_src_info
# import os
#
# < ... your code ... >
#
# if __name__ == "__main__":
# print_src_info.src_info(os.path.basename(__file__))
## example output:
#
# ################################################################################
# runtime ~= 0.049 ms
# source file --> 'print_src_info.py'
# output via --> '__main__'
# modules used in 'print_src_info':
# 'importlib':=implib 'inspect':=inspect 'os':=os 'textwrap':=twrap
# 'timeit':=timeit
# ################################################################################
For the majority of scripts which only import modules at the top level, it is quite sufficient to load the file as a module, and scan its members for modules:
import sys,io,imp,types
scriptname = 'myfile.py'
with io.open(scriptname) as scriptfile:
code = compile(scriptfile.readall(),scriptname,'exec')
newmodule = imp.new_module('__main__')
exec(codeobj,newmodule.__dict__)
scriptmodules = [name for name in dir(newmodule) if isinstance(newmodule.__dict__[name],types.ModuleType)]
This simulates the module being run as a script, by setting the module's name to '__main__'. It should therefore also capture funky dynamic module loading. The only modules it won't capture are those which are imported only into local scopes.
It's actually working quite good with
print [key for key in locals().keys()
if isinstance(locals()[key], type(sys)) and not key.startswith('__')]
I understand that this post is VERY old but I have found an ideal solution.
I came up with this idea:
def find_modules(code):
modules = []
code = code.splitlines()
for item in code:
if item[:7] == "import " and ", " not in item:
if " as " in item:
modules.append(item[7:item.find(" as ")])
else:
modules.append(item[7:])
elif item[:5] == "from ":
modules.append(item[5:item.find(" import ")])
elif ", " in item:
item = item[7:].split(", ")
modules = modules+item
else:
print(item)
return modules
code = """
import foo
import bar
from baz import eggs
import mymodule as test
import hello, there, stack
"""
print(find_modules(code))
it does from, as, commas and normal import statements.
it requires no dependencies and works with other lines of code.
The above code prints:
['foo', 'bar', 'baz', 'mymodule', 'hello', 'there', 'stack']
Just put your code in the find_modules function.
I'm editing my original answer to say this. This is doable with a code snippet like the one below, but parsing the AST may be the best way to go.
def iter_imports(fd):
""" Yield only lines that appear to be imports from an iterable.
fd can be an open file, a list of lines, etc.
"""
for line in fd:
trimmed = line.strip()
if trimmed.startswith('import '):
yield trimmed
elif trimmed.startswith('from ') and ('import ' in trimmed):
yield trimmed
def main():
# File name to read.
filename = '/my/path/myfile.py'
# Safely open the file, exit on error
try:
with open(filename) as f:
# Iterate over the lines in this file, and generate a list of
# lines that appear to be imports.
import_lines = list(iter_imports(f))
except (IOError, OSError) as exIO:
print('Error opening file: {}\n{}'.format(filename, exIO))
return 1
else:
# From here, import_lines should be a list of lines like this:
# from module import thing
# import os, sys
# from module import *
# Do whatever you need to do with the import lines.
print('\n'.join(import_lines))
return 0
if __name__ == '__main__':
sys.exit(main())
Further string parsing will be needed to grab just the module names. This does not catch cases where multi-line strings or doc strings contain the words 'import ' or 'from X import '. This is why I suggested parsing the AST.