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