Source code for zope.deferredimport.deferredmodule

##############################################################################
#
# Copyright (c) 2006 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Modules with defered attributes
"""
import sys
import warnings

import zope.proxy


class Deferred:

    def __init__(self, name, specifier):
        self.__name__ = name
        self.specifier = specifier

    _import_chicken = {}, {}, ['*']

    def get(self):

        specifier = self.specifier
        if ':' in specifier:
            module, name = specifier.split(':')
        else:
            module, name = specifier, ''

        v = __import__(module, *self._import_chicken)
        if name:
            for n in name.split('.'):
                v = getattr(v, n)
        return v


class DeferredAndDeprecated(Deferred):

    def __init__(self, name, specifier, message):
        super().__init__(name, specifier)
        self.message = message

    def get(self):
        warnings.warn(
            self.__name__ + " is deprecated. " + self.message,
            DeprecationWarning, stacklevel=3)

        return super().get()


class ModuleProxy(zope.proxy.ProxyBase):
    __slots__ = ('__deferred_definitions__', '__doc__')

    def __init__(self, module):
        super().__init__(module)
        self.__deferred_definitions__ = {}
        self.__doc__ = module.__doc__

    def __getattr__(self, name):
        try:
            get = self.__deferred_definitions__[name]
        except KeyError:
            raise AttributeError(name)
        v = get.get()
        setattr(self, name, v)
        try:
            del self.__deferred_definitions__[name]
        except KeyError:  # pragma: no cover
            pass
        return v


[docs] def initialize(level=1): """Prepare a module to support deferred imports. Modules do not need to call this directly, because the `define*` and `deprecated*` functions call it. This is intended to be called from the module to be prepared. The implementation wraps a proxy around the module and replaces the entry in sys.modules with the proxy. It does no harm to call this function more than once for a given module, because this function does not re-wrap a proxied module. The level parameter specifies a relative stack depth. When this function is called directly by the module, level should be 1. When this function is called by a helper function, level should increase with the depth of the stack. Returns nothing when level is 1; otherwise returns the proxied module. """ __name__ = sys._getframe(level).f_globals['__name__'] module = sys.modules[__name__] if type(module) is not ModuleProxy: module = ModuleProxy(module) sys.modules[__name__] = module if level == 1: return return module
[docs] def define(**names): """Define deferred imports using keyword parameters. Each parameter specifies the importable name and how to import it. Use `module:name` syntax to import a name from a module, or `module` (no colon) to import a module. """ module = initialize(2) __deferred_definitions__ = module.__deferred_definitions__ for name, specifier in names.items(): __deferred_definitions__[name] = Deferred(name, specifier)
[docs] def defineFrom(from_name, *names): """Define deferred imports from a particular module. The from_name specifies which module to import. The rest of the parameters specify names to import from that module. """ module = initialize(2) __deferred_definitions__ = module.__deferred_definitions__ for name in names: specifier = from_name + ':' + name __deferred_definitions__[name] = Deferred(name, specifier)
[docs] def deprecated(message, **names): """Define deferred and deprecated imports using keyword parameters. The first use of each name will generate a deprecation warning with the given message. Each parameter specifies the importable name and how to import it. Use `module:name` syntax to import a name from a module, or `module` (no colon) to import a module. """ module = initialize(2) __deferred_definitions__ = module.__deferred_definitions__ for name, specifier in names.items(): __deferred_definitions__[name] = DeferredAndDeprecated( name, specifier, message)
[docs] def deprecatedFrom(message, from_name, *names): """Define deferred and deprecated imports from a particular module. The first use of each name will generate a deprecation warning with the given message. The from_name specifies which module to import. The rest of the parameters specify names to import from that module. """ module = initialize(2) __deferred_definitions__ = module.__deferred_definitions__ for name in names: specifier = from_name + ':' + name __deferred_definitions__[name] = DeferredAndDeprecated( name, specifier, message)