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