123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #!/usr/bin/env python
- """
- Import diagnostics. Run bin/diagnose_imports.py --help for details.
- """
- from __future__ import annotations
- if __name__ == "__main__":
- import sys
- import inspect
- import builtins
- import optparse
- from os.path import abspath, dirname, join, normpath
- this_file = abspath(__file__)
- sympy_dir = join(dirname(this_file), '..', '..', '..')
- sympy_dir = normpath(sympy_dir)
- sys.path.insert(0, sympy_dir)
- option_parser = optparse.OptionParser(
- usage=
- "Usage: %prog option [options]\n"
- "\n"
- "Import analysis for imports between SymPy modules.")
- option_group = optparse.OptionGroup(
- option_parser,
- 'Analysis options',
- 'Options that define what to do. Exactly one of these must be given.')
- option_group.add_option(
- '--problems',
- help=
- 'Print all import problems, that is: '
- 'If an import pulls in a package instead of a module '
- '(e.g. sympy.core instead of sympy.core.add); ' # see ##PACKAGE##
- 'if it imports a symbol that is already present; ' # see ##DUPLICATE##
- 'if it imports a symbol '
- 'from somewhere other than the defining module.', # see ##ORIGIN##
- action='count')
- option_group.add_option(
- '--origins',
- help=
- 'For each imported symbol in each module, '
- 'print the module that defined it. '
- '(This is useful for import refactoring.)',
- action='count')
- option_parser.add_option_group(option_group)
- option_group = optparse.OptionGroup(
- option_parser,
- 'Sort options',
- 'These options define the sort order for output lines. '
- 'At most one of these options is allowed. '
- 'Unsorted output will reflect the order in which imports happened.')
- option_group.add_option(
- '--by-importer',
- help='Sort output lines by name of importing module.',
- action='count')
- option_group.add_option(
- '--by-origin',
- help='Sort output lines by name of imported module.',
- action='count')
- option_parser.add_option_group(option_group)
- (options, args) = option_parser.parse_args()
- if args:
- option_parser.error(
- 'Unexpected arguments %s (try %s --help)' % (args, sys.argv[0]))
- if options.problems > 1:
- option_parser.error('--problems must not be given more than once.')
- if options.origins > 1:
- option_parser.error('--origins must not be given more than once.')
- if options.by_importer > 1:
- option_parser.error('--by-importer must not be given more than once.')
- if options.by_origin > 1:
- option_parser.error('--by-origin must not be given more than once.')
- options.problems = options.problems == 1
- options.origins = options.origins == 1
- options.by_importer = options.by_importer == 1
- options.by_origin = options.by_origin == 1
- if not options.problems and not options.origins:
- option_parser.error(
- 'At least one of --problems and --origins is required')
- if options.problems and options.origins:
- option_parser.error(
- 'At most one of --problems and --origins is allowed')
- if options.by_importer and options.by_origin:
- option_parser.error(
- 'At most one of --by-importer and --by-origin is allowed')
- options.by_process = not options.by_importer and not options.by_origin
- builtin_import = builtins.__import__
- class Definition:
- """Information about a symbol's definition."""
- def __init__(self, name, value, definer):
- self.name = name
- self.value = value
- self.definer = definer
- def __hash__(self):
- return hash(self.name)
- def __eq__(self, other):
- return self.name == other.name and self.value == other.value
- def __ne__(self, other):
- return not (self == other)
- def __repr__(self):
- return 'Definition(%s, ..., %s)' % (
- repr(self.name), repr(self.definer))
- # Maps each function/variable to name of module to define it
- symbol_definers: dict[Definition, str] = {}
- def in_module(a, b):
- """Is a the same module as or a submodule of b?"""
- return a == b or a != None and b != None and a.startswith(b + '.')
- def relevant(module):
- """Is module relevant for import checking?
- Only imports between relevant modules will be checked."""
- return in_module(module, 'sympy')
- sorted_messages = []
- def msg(msg, *args):
- global options, sorted_messages
- if options.by_process:
- print(msg % args)
- else:
- sorted_messages.append(msg % args)
- def tracking_import(module, globals=globals(), locals=[], fromlist=None, level=-1):
- """__import__ wrapper - does not change imports at all, but tracks them.
- Default order is implemented by doing output directly.
- All other orders are implemented by collecting output information into
- a sorted list that will be emitted after all imports are processed.
- Indirect imports can only occur after the requested symbol has been
- imported directly (because the indirect import would not have a module
- to pick the symbol up from).
- So this code detects indirect imports by checking whether the symbol in
- question was already imported.
- Keeps the semantics of __import__ unchanged."""
- global options, symbol_definers
- caller_frame = inspect.getframeinfo(sys._getframe(1))
- importer_filename = caller_frame.filename
- importer_module = globals['__name__']
- if importer_filename == caller_frame.filename:
- importer_reference = '%s line %s' % (
- importer_filename, str(caller_frame.lineno))
- else:
- importer_reference = importer_filename
- result = builtin_import(module, globals, locals, fromlist, level)
- importee_module = result.__name__
- # We're only interested if importer and importee are in SymPy
- if relevant(importer_module) and relevant(importee_module):
- for symbol in result.__dict__.iterkeys():
- definition = Definition(
- symbol, result.__dict__[symbol], importer_module)
- if definition not in symbol_definers:
- symbol_definers[definition] = importee_module
- if hasattr(result, '__path__'):
- ##PACKAGE##
- # The existence of __path__ is documented in the tutorial on modules.
- # Python 3.3 documents this in http://docs.python.org/3.3/reference/import.html
- if options.by_origin:
- msg('Error: %s (a package) is imported by %s',
- module, importer_reference)
- else:
- msg('Error: %s contains package import %s',
- importer_reference, module)
- if fromlist != None:
- symbol_list = fromlist
- if '*' in symbol_list:
- if (importer_filename.endswith('__init__.py')
- or importer_filename.endswith('__init__.pyc')
- or importer_filename.endswith('__init__.pyo')):
- # We do not check starred imports inside __init__
- # That's the normal "please copy over its imports to my namespace"
- symbol_list = []
- else:
- symbol_list = result.__dict__.iterkeys()
- for symbol in symbol_list:
- if symbol not in result.__dict__:
- if options.by_origin:
- msg('Error: %s.%s is not defined (yet), but %s tries to import it',
- importee_module, symbol, importer_reference)
- else:
- msg('Error: %s tries to import %s.%s, which did not define it (yet)',
- importer_reference, importee_module, symbol)
- else:
- definition = Definition(
- symbol, result.__dict__[symbol], importer_module)
- symbol_definer = symbol_definers[definition]
- if symbol_definer == importee_module:
- ##DUPLICATE##
- if options.by_origin:
- msg('Error: %s.%s is imported again into %s',
- importee_module, symbol, importer_reference)
- else:
- msg('Error: %s imports %s.%s again',
- importer_reference, importee_module, symbol)
- else:
- ##ORIGIN##
- if options.by_origin:
- msg('Error: %s.%s is imported by %s, which should import %s.%s instead',
- importee_module, symbol, importer_reference, symbol_definer, symbol)
- else:
- msg('Error: %s imports %s.%s but should import %s.%s instead',
- importer_reference, importee_module, symbol, symbol_definer, symbol)
- return result
- builtins.__import__ = tracking_import
- __import__('sympy')
- sorted_messages.sort()
- for message in sorted_messages:
- print(message)
|