preloaded.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. # util/preloaded.py
  2. # Copyright (C) 2005-2025 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. # mypy: allow-untyped-defs, allow-untyped-calls
  8. """supplies the "preloaded" registry to resolve circular module imports at
  9. runtime.
  10. """
  11. from __future__ import annotations
  12. import sys
  13. from typing import Any
  14. from typing import Callable
  15. from typing import TYPE_CHECKING
  16. from typing import TypeVar
  17. _FN = TypeVar("_FN", bound=Callable[..., Any])
  18. if TYPE_CHECKING:
  19. from sqlalchemy import dialects as _dialects
  20. from sqlalchemy import orm as _orm
  21. from sqlalchemy.engine import cursor as _engine_cursor
  22. from sqlalchemy.engine import default as _engine_default
  23. from sqlalchemy.engine import reflection as _engine_reflection
  24. from sqlalchemy.engine import result as _engine_result
  25. from sqlalchemy.engine import url as _engine_url
  26. from sqlalchemy.orm import attributes as _orm_attributes
  27. from sqlalchemy.orm import base as _orm_base
  28. from sqlalchemy.orm import clsregistry as _orm_clsregistry
  29. from sqlalchemy.orm import decl_api as _orm_decl_api
  30. from sqlalchemy.orm import decl_base as _orm_decl_base
  31. from sqlalchemy.orm import dependency as _orm_dependency
  32. from sqlalchemy.orm import descriptor_props as _orm_descriptor_props
  33. from sqlalchemy.orm import mapperlib as _orm_mapper
  34. from sqlalchemy.orm import properties as _orm_properties
  35. from sqlalchemy.orm import relationships as _orm_relationships
  36. from sqlalchemy.orm import session as _orm_session
  37. from sqlalchemy.orm import state as _orm_state
  38. from sqlalchemy.orm import strategies as _orm_strategies
  39. from sqlalchemy.orm import strategy_options as _orm_strategy_options
  40. from sqlalchemy.orm import util as _orm_util
  41. from sqlalchemy.sql import default_comparator as _sql_default_comparator
  42. from sqlalchemy.sql import dml as _sql_dml
  43. from sqlalchemy.sql import elements as _sql_elements
  44. from sqlalchemy.sql import functions as _sql_functions
  45. from sqlalchemy.sql import naming as _sql_naming
  46. from sqlalchemy.sql import schema as _sql_schema
  47. from sqlalchemy.sql import selectable as _sql_selectable
  48. from sqlalchemy.sql import sqltypes as _sql_sqltypes
  49. from sqlalchemy.sql import traversals as _sql_traversals
  50. from sqlalchemy.sql import util as _sql_util
  51. # sigh, appease mypy 0.971 which does not accept imports as instance
  52. # variables of a module
  53. dialects = _dialects
  54. engine_cursor = _engine_cursor
  55. engine_default = _engine_default
  56. engine_reflection = _engine_reflection
  57. engine_result = _engine_result
  58. engine_url = _engine_url
  59. orm_clsregistry = _orm_clsregistry
  60. orm_base = _orm_base
  61. orm = _orm
  62. orm_attributes = _orm_attributes
  63. orm_decl_api = _orm_decl_api
  64. orm_decl_base = _orm_decl_base
  65. orm_descriptor_props = _orm_descriptor_props
  66. orm_dependency = _orm_dependency
  67. orm_mapper = _orm_mapper
  68. orm_properties = _orm_properties
  69. orm_relationships = _orm_relationships
  70. orm_session = _orm_session
  71. orm_strategies = _orm_strategies
  72. orm_strategy_options = _orm_strategy_options
  73. orm_state = _orm_state
  74. orm_util = _orm_util
  75. sql_default_comparator = _sql_default_comparator
  76. sql_dml = _sql_dml
  77. sql_elements = _sql_elements
  78. sql_functions = _sql_functions
  79. sql_naming = _sql_naming
  80. sql_selectable = _sql_selectable
  81. sql_traversals = _sql_traversals
  82. sql_schema = _sql_schema
  83. sql_sqltypes = _sql_sqltypes
  84. sql_util = _sql_util
  85. class _ModuleRegistry:
  86. """Registry of modules to load in a package init file.
  87. To avoid potential thread safety issues for imports that are deferred
  88. in a function, like https://bugs.python.org/issue38884, these modules
  89. are added to the system module cache by importing them after the packages
  90. has finished initialization.
  91. A global instance is provided under the name :attr:`.preloaded`. Use
  92. the function :func:`.preload_module` to register modules to load and
  93. :meth:`.import_prefix` to load all the modules that start with the
  94. given path.
  95. While the modules are loaded in the global module cache, it's advisable
  96. to access them using :attr:`.preloaded` to ensure that it was actually
  97. registered. Each registered module is added to the instance ``__dict__``
  98. in the form `<package>_<module>`, omitting ``sqlalchemy`` from the package
  99. name. Example: ``sqlalchemy.sql.util`` becomes ``preloaded.sql_util``.
  100. """
  101. def __init__(self, prefix="sqlalchemy."):
  102. self.module_registry = set()
  103. self.prefix = prefix
  104. def preload_module(self, *deps: str) -> Callable[[_FN], _FN]:
  105. """Adds the specified modules to the list to load.
  106. This method can be used both as a normal function and as a decorator.
  107. No change is performed to the decorated object.
  108. """
  109. self.module_registry.update(deps)
  110. return lambda fn: fn
  111. def import_prefix(self, path: str) -> None:
  112. """Resolve all the modules in the registry that start with the
  113. specified path.
  114. """
  115. for module in self.module_registry:
  116. if self.prefix:
  117. key = module.split(self.prefix)[-1].replace(".", "_")
  118. else:
  119. key = module
  120. if (
  121. not path or module.startswith(path)
  122. ) and key not in self.__dict__:
  123. __import__(module, globals(), locals())
  124. self.__dict__[key] = globals()[key] = sys.modules[module]
  125. _reg = _ModuleRegistry()
  126. preload_module = _reg.preload_module
  127. import_prefix = _reg.import_prefix
  128. # this appears to do absolutely nothing for any version of mypy
  129. # if TYPE_CHECKING:
  130. # def __getattr__(key: str) -> ModuleType:
  131. # ...