Thinking about pyinstaller and python 3 it occurred to me that it should be
possible to get a complete list of what a script imports in a somewhat
simpler way than pyinstaller presently does it.

Below find a script that first imports a target script and then executes
it. It copies sys.modules at each point, and in this way it finds out what
has been imported.

This script is incomplete. I would expect in finished form it would take
the target script name as a command argument, along with some options for
example "--no-exec" if it is not necessary to execute the script. (But I
was surprised to find that a PyQt5 app added quite a few modules during
execution that were not loaded during its import.)

The output at present is just a list of modules required at each phase of
the run. However, it would be trivial to output a pyinstaller TOC object in
source form, or a set of TOCs by filtering the data for binaries, etc.

Here's the script, try it out (you have to set script_name= ).

#================================
import sys #everybody imports sys

# Define the target script, this would be given as a command argument

script_name = 'my_script' # some moderately complex GUI app here

# Record the modules needed to get this script going to this point.

module_list_base = sys.modules.copy()

# Import the target script and record what it imported.

script_module = __import__(script_name)

module_list_static_import = sys.modules.copy()

# Conditionally, execute the target script to capture dynamic imports.
# e.g., if a --no-exec option is not given

# Import what we need, we take these out of the inventory later.

import imp
import importlib
import marshal

# Create a code object by un-marshalling the cached .pyc if any
cache_code = None
if hasattr(script_module,'__cached__') and script_module.__cached__ :
    bytecode_file = open(script_module.__cached__,'rb')
    magic = bytecode_file.read(4)
    if magic == imp.get_magic() :
        timestamp = bytecode_file.read(4)
        padding = bytecode_file.read(4)
        bytecode_blob = bytecode_file.read()
        cache_code = marshal.loads(bytecode_blob)
    bytecode_file.close()

if cache_code is None :
    # No .pyc, create code object via compiling source
    script_file = script_module.__file__
    source_file = open(script_file,'r',encoding='UTF-8')
    source_blob = source_file.read()
    source_file.close()
    cache_code = compile(source_blob,'<string>','exec')

# Now get rid of the sys.modules entries for things we imported
# to do that compile. If the executed app uses them, they will
# be reloaded and go back into the list. "marshal" is built-in.

del sys.modules['imp']
del sys.modules['importlib']
del sys.modules['importlib._bootstrap']
del sys.modules['importlib.machinery']

# Actually execute the code

exec(cache_code)

# Snapshot the total of imports after execution

module_list_dynamic_import = sys.modules.copy()

def dict_diff(da, db):
    '''Return a dict containing only
    members of da that are not in db: da-db'''
    ka = set(da.keys())
    kb = set(db.keys())
    kd = ka - kb
    return { k:da[k] for k in kd }

def print_module_dict(da):
    '''Print the names and filepaths of modules in e.g. sys.modules'''
    for k in sorted(da.keys()):
        f = '(builtin)'
        if hasattr(da[k], '__file__') :
            f = da[k].__file__
        print( k, f)

print('\n==== Essential base modules =====\n')
print_module_dict(module_list_base)
print('\n==== Static imports by script',script_name,'====\n')
print_module_dict( dict_diff(module_list_static_import,module_list_base) )
print('\n==== Modules added by executing script',script_name,'====\n')
print_module_dict( dict_diff(module_list_dynamic_import,
module_list_static_import) )

#================================

-- 
You received this message because you are subscribed to the Google Groups 
"PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/pyinstaller.
For more options, visit https://groups.google.com/d/optout.

Reply via email to