_preloaded.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. # util/_preloaded.py
  2. # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. """supplies the "preloaded" registry to resolve circular module imports at
  8. runtime.
  9. """
  10. import sys
  11. from . import compat
  12. class _ModuleRegistry:
  13. """Registry of modules to load in a package init file.
  14. To avoid potential thread safety issues for imports that are deferred
  15. in a function, like https://bugs.python.org/issue38884, these modules
  16. are added to the system module cache by importing them after the packages
  17. has finished initialization.
  18. A global instance is provided under the name :attr:`.preloaded`. Use
  19. the function :func:`.preload_module` to register modules to load and
  20. :meth:`.import_prefix` to load all the modules that start with the
  21. given path.
  22. While the modules are loaded in the global module cache, it's advisable
  23. to access them using :attr:`.preloaded` to ensure that it was actually
  24. registered. Each registered module is added to the instance ``__dict__``
  25. in the form `<package>_<module>`, omitting ``sqlalchemy`` from the package
  26. name. Example: ``sqlalchemy.sql.util`` becomes ``preloaded.sql_util``.
  27. """
  28. def __init__(self, prefix="sqlalchemy."):
  29. self.module_registry = set()
  30. self.prefix = prefix
  31. def preload_module(self, *deps):
  32. """Adds the specified modules to the list to load.
  33. This method can be used both as a normal function and as a decorator.
  34. No change is performed to the decorated object.
  35. """
  36. self.module_registry.update(deps)
  37. return lambda fn: fn
  38. def import_prefix(self, path):
  39. """Resolve all the modules in the registry that start with the
  40. specified path.
  41. """
  42. for module in self.module_registry:
  43. if self.prefix:
  44. key = module.split(self.prefix)[-1].replace(".", "_")
  45. else:
  46. key = module
  47. if (
  48. not path or module.startswith(path)
  49. ) and key not in self.__dict__:
  50. compat.import_(module, globals(), locals())
  51. self.__dict__[key] = sys.modules[module]
  52. preloaded = _ModuleRegistry()
  53. preload_module = preloaded.preload_module