1############################################################################## 2# 3# Copyright (c) 2006 Zope Foundation and Contributors. 4# All Rights Reserved. 5# 6# This software is subject to the provisions of the Zope Public License, 7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11# FOR A PARTICULAR PURPOSE. 12# 13############################################################################## 14"""Modules with defered attributes 15""" 16import sys 17import warnings 18import zope.proxy 19 20 21class Deferred(object): 22 23 def __init__(self, name, specifier): 24 self.__name__ = name 25 self.specifier = specifier 26 27 _import_chicken = {}, {}, ['*'] 28 29 def get(self): 30 31 specifier = self.specifier 32 if ':' in specifier: 33 module, name = specifier.split(':') 34 else: 35 module, name = specifier, '' 36 37 v = __import__(module, *self._import_chicken) 38 if name: 39 for n in name.split('.'): 40 v = getattr(v, n) 41 return v 42 43class DeferredAndDeprecated(Deferred): 44 45 def __init__(self, name, specifier, message): 46 super(DeferredAndDeprecated, self).__init__(name, specifier) 47 self.message = message 48 49 def get(self): 50 warnings.warn( 51 self.__name__ + " is deprecated. " + self.message, 52 DeprecationWarning, stacklevel=3) 53 54 return super(DeferredAndDeprecated, self).get() 55 56 57class ModuleProxy(zope.proxy.ProxyBase): 58 __slots__ = ('__deferred_definitions__', ) 59 60 def __init__(self, module): 61 super(ModuleProxy, self).__init__(module) 62 self.__deferred_definitions__ = {} 63 64 def __getattr__(self, name): 65 try: 66 get = self.__deferred_definitions__.pop(name) 67 except KeyError: 68 raise AttributeError, name 69 v = get.get() 70 setattr(self, name, v) 71 return v 72 73def initialize(level=1): 74 """Prepare a module to support deferred imports. 75 76 Modules do not need to call this directly, because the 77 `define*` and `deprecated*` functions call it. 78 79 This is intended to be called from the module to be prepared. 80 The implementation wraps a proxy around the module and replaces 81 the entry in sys.modules with the proxy. It does no harm to 82 call this function more than once for a given module, because 83 this function does not re-wrap a proxied module. 84 85 The level parameter specifies a relative stack depth. 86 When this function is called directly by the module, level should be 1. 87 When this function is called by a helper function, level should 88 increase with the depth of the stack. 89 90 Returns nothing when level is 1; otherwise returns the proxied module. 91 """ 92 __name__ = sys._getframe(level).f_globals['__name__'] 93 module = sys.modules[__name__] 94 if not (type(module) is ModuleProxy): 95 module = ModuleProxy(module) 96 sys.modules[__name__] = module 97 98 if level == 1: 99 return 100 return module 101 102def define(**names): 103 """Define deferred imports using keyword parameters. 104 105 Each parameter specifies the importable name and how to import it. 106 Use `module:name` syntax to import a name from a module, or `module` 107 (no colon) to import a module. 108 """ 109 module = initialize(2) 110 __deferred_definitions__ = module.__deferred_definitions__ 111 for name, specifier in names.iteritems(): 112 __deferred_definitions__[name] = Deferred(name, specifier) 113 114def defineFrom(from_name, *names): 115 """Define deferred imports from a particular module. 116 117 The from_name specifies which module to import. 118 The rest of the parameters specify names to import from that module. 119 """ 120 module = initialize(2) 121 __deferred_definitions__ = module.__deferred_definitions__ 122 for name in names: 123 specifier = from_name + ':' + name 124 __deferred_definitions__[name] = Deferred(name, specifier) 125 126def deprecated(message, **names): 127 """Define deferred and deprecated imports using keyword parameters. 128 129 The first use of each name will generate a deprecation warning with 130 the given message. 131 132 Each parameter specifies the importable name and how to import it. 133 Use `module:name` syntax to import a name from a module, or `module` 134 (no colon) to import a module. 135 """ 136 module = initialize(2) 137 __deferred_definitions__ = module.__deferred_definitions__ 138 for name, specifier in names.iteritems(): 139 __deferred_definitions__[name] = DeferredAndDeprecated( 140 name, specifier, message) 141 142def deprecatedFrom(message, from_name, *names): 143 """Define deferred and deprecated imports from a particular module. 144 145 The first use of each name will generate a deprecation warning with 146 the given message. 147 148 The from_name specifies which module to import. 149 The rest of the parameters specify names to import from that module. 150 """ 151 module = initialize(2) 152 __deferred_definitions__ = module.__deferred_definitions__ 153 for name in names: 154 specifier = from_name + ':' + name 155 __deferred_definitions__[name] = DeferredAndDeprecated( 156 name, specifier, message) 157