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