File: mixedmodule.py

package info (click to toggle)
pypy3 7.0.0%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 111,848 kB
  • sloc: python: 1,291,746; ansic: 74,281; asm: 5,187; cpp: 3,017; sh: 2,533; makefile: 544; xml: 243; lisp: 45; csh: 21; awk: 4
file content (264 lines) | stat: -rw-r--r-- 10,210 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
from pypy.interpreter.module import Module, init_extra_module_attrs
from pypy.interpreter.function import Function, BuiltinFunction
from pypy.interpreter import gateway
from pypy.interpreter.error import OperationError
from pypy.interpreter.baseobjspace import W_Root

from rpython.rlib.objectmodel import not_rpython

import sys

class MixedModule(Module):
    applevel_name = None

    # The following attribute is None as long as the module has not been
    # imported yet, and when it has been, it is mod.__dict__.copy() just
    # after startup().
    w_initialdict = None
    lazy = False
    submodule_name = None

    @not_rpython
    def __init__(self, space, w_name):
        Module.__init__(self, space, w_name)
        init_extra_module_attrs(space, self)
        self.lazy = True
        self.lazy_initial_values_w = {}
        self.__class__.buildloaders()
        self.loaders = self.loaders.copy()    # copy from the class to the inst
        self.submodules_w = []

    @not_rpython
    def install(self):
        """install this module, and it's submodules into
        space.builtin_modules"""
        Module.install(self)
        if hasattr(self, "submodules"):
            space = self.space
            name = space.text_w(self.w_name)
            for sub_name, module_cls in self.submodules.iteritems():
                if module_cls.submodule_name is None:
                    module_cls.submodule_name = sub_name
                module_name = space.newtext("%s.%s" % (name, sub_name))
                m = module_cls(space, module_name)
                m.install()
                self.submodules_w.append(m)

    def init(self, space):
        """This is called each time the module is imported or reloaded
        """
        if self.w_initialdict is not None:
            # the module was already imported.  Refresh its content with
            # the saved dict, as done with built-in and extension modules
            # on CPython.
            space.call_method(self.w_dict, 'update', self.w_initialdict)

        for w_submodule in self.submodules_w:
            name = space.text0_w(w_submodule.w_name)
            space.setitem(self.w_dict, space.newtext(name.split(".")[-1]), w_submodule)
            space.getbuiltinmodule(name)

        if self.w_initialdict is None:
            Module.init(self, space)
            if not self.lazy and self.w_initialdict is None:
                self.save_module_content_for_future_reload()

    def save_module_content_for_future_reload(self):
        # Save the current dictionary in w_initialdict, for future
        # reloads.  This forces the dictionary if needed.
        w_dict = self.getdict(self.space)
        self.w_initialdict = self.space.call_method(w_dict, 'copy')

    @classmethod
    @not_rpython
    def get_applevel_name(cls):
        if cls.applevel_name is not None:
            return cls.applevel_name
        else:
            pkgroot = cls.__module__
            return pkgroot.split('.')[-1]

    def get(self, name):
        space = self.space
        w_value = self.getdictvalue(space, name)
        if w_value is None:
            raise OperationError(space.w_AttributeError, space.newtext(name))
        return w_value

    def call(self, name, *args_w):
        w_builtin = self.get(name)
        return self.space.call_function(w_builtin, *args_w)

    def getdictvalue(self, space, name):
        w_value = space.finditem_str(self.w_dict, name)
        if self.lazy and w_value is None:
            return self._load_lazily(space, name)
        return w_value

    def setdictvalue(self, space, attr, w_value):
        if self.lazy and attr not in self.lazy_initial_values_w:
            # in lazy mode, the first time an attribute changes,
            # we save away the old (initial) value.  This allows
            # a future getdict() call to build the correct
            # self.w_initialdict, containing the initial value.
            w_initial_value = self._load_lazily(space, attr)
            self.lazy_initial_values_w[attr] = w_initial_value
        space.setitem_str(self.w_dict, attr, w_value)
        return True

    def _load_lazily(self, space, name):
        w_name = space.new_interned_str(name)
        try:
            loader = self.loaders[name]
        except KeyError:
            return None
        else:
            w_value = loader(space)
            # the idea of the following code is that all functions that are
            # directly in a mixed-module are "builtin", e.g. they get a
            # special type without a __get__
            # note that this is not just all functions that contain a
            # builtin code object, as e.g. methods of builtin types have to
            # be normal Functions to get the correct binding behaviour
            func = w_value
            if (isinstance(func, Function) and
                    type(func) is not BuiltinFunction):
                try:
                    bltin = func._builtinversion_
                except AttributeError:
                    bltin = BuiltinFunction(func)
                    bltin.w_module = self.w_name
                    func._builtinversion_ = bltin
                    bltin.name = name
                    bltin.qualname = bltin.name.decode('utf-8')
                w_value = bltin
            space.setitem(self.w_dict, w_name, w_value)
            return w_value

    def getdict(self, space):
        if self.lazy:
            self._force_lazy_dict_now()
        return self.w_dict

    def _force_lazy_dict_now(self):
        # Force the dictionary by calling all lazy loaders now.
        # This also saves in self.w_initialdict a copy of all the
        # initial values, including if they have already been
        # modified by setdictvalue().
        space = self.space
        for name in self.loaders:
            w_value = self.get(name)
            space.setitem(self.w_dict, space.new_interned_str(name), w_value)
        self.lazy = False
        self.save_module_content_for_future_reload()
        for key, w_initial_value in self.lazy_initial_values_w.items():
            w_key = space.new_interned_str(key)
            if w_initial_value is not None:
                space.setitem(self.w_initialdict, w_key, w_initial_value)
            else:
                if space.finditem(self.w_initialdict, w_key) is not None:
                    space.delitem(self.w_initialdict, w_key)
        del self.lazy_initial_values_w

    def _cleanup_(self):
        self.getdict(self.space)
        self.w_initialdict = None
        self.startup_called = False
        self._frozen = True

    @classmethod
    @not_rpython
    def buildloaders(cls):
        if not hasattr(cls, 'loaders'):
            # build a constant dictionary out of
            # applevel/interplevel definitions
            cls.loaders = loaders = {}
            pkgroot = cls.__module__
            appname = cls.get_applevel_name()
            if cls.submodule_name is not None:
                appname += '.%s' % (cls.submodule_name,)
            for name, spec in cls.interpleveldefs.items():
                loaders[name] = getinterpevalloader(pkgroot, spec)
            for name, spec in cls.appleveldefs.items():
                loaders[name] = getappfileloader(pkgroot, appname, spec)
            assert '__file__' not in loaders
            if '__doc__' not in loaders:
                loaders['__doc__'] = cls.get__doc__

    def extra_interpdef(self, name, spec):
        cls = self.__class__
        pkgroot = cls.__module__
        loader = getinterpevalloader(pkgroot, spec)
        space = self.space
        w_obj = loader(space)
        space.setattr(self, space.newtext(name), w_obj)

    @classmethod
    def get__doc__(cls, space):
        return space.newtext_or_none(cls.__doc__)


@not_rpython
def getinterpevalloader(pkgroot, spec):
    def ifileloader(space):
        d = {'space': space}
        # EVIL HACK (but it works, and this is not RPython :-)
        while 1:
            try:
                value = eval(spec, d)
            except NameError as ex:
                name = ex.args[0].split("'")[1]  # super-Evil
                if name in d:
                    raise   # propagate the NameError
                try:
                    d[name] = __import__(pkgroot+'.'+name, None, None, [name])
                except ImportError:
                    etype, evalue, etb = sys.exc_info()
                    try:
                        d[name] = __import__(name, None, None, [name])
                    except ImportError:
                        # didn't help, re-raise the original exception for
                        # clarity
                        raise etype, evalue, etb
            else:
                #print spec, "->", value
                if hasattr(value, 'func_code'):  # semi-evil
                    return gateway.interp2app(value).get_function(space)

                try:
                    is_type = issubclass(value, W_Root)  # pseudo-evil
                except TypeError:
                    is_type = False
                if is_type:
                    return space.gettypefor(value)

                assert isinstance(value, W_Root), (
                    "interpleveldef %s.%s must return a wrapped object "
                    "(got %r instead)" % (pkgroot, spec, value))
                return value
    return ifileloader

applevelcache = {}
@not_rpython
def getappfileloader(pkgroot, appname, spec):
    # hum, it's a bit more involved, because we usually
    # want the import at applevel
    modname, attrname = spec.split('.')
    impbase = pkgroot + '.' + modname
    try:
        app = applevelcache[impbase]
    except KeyError:
        import imp
        pkg = __import__(pkgroot, None, None, ['__doc__'])
        file, fn, (suffix, mode, typ) = imp.find_module(modname, pkg.__path__)
        assert typ == imp.PY_SOURCE
        source = file.read()
        file.close()
        if fn.endswith('.pyc'):
            fn = fn[:-1]
        app = gateway.applevel(source, filename=fn, modname=appname)
        applevelcache[impbase] = app

    def afileloader(space):
        return app.wget(space, attrname)
    return afileloader