File: zopeinterface.py

package info (click to toggle)
pydoctor 19.11.0%2Bgit20200303.47424e7-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 1,400 kB
  • sloc: python: 6,290; makefile: 11; sh: 8
file content (284 lines) | stat: -rw-r--r-- 10,117 bytes parent folder | download | duplicates (2)
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
"""Support for Zope interfaces."""

from __future__ import print_function

import ast
import re

import astor
from pydoctor import astbuilder, model
from six import text_type


class ZopeInterfaceModule(model.Module):
    def setup(self):
        super(ZopeInterfaceModule, self).setup()
        self.implements_directly = [] # [name of interface]

    @property
    def allImplementedInterfaces(self):
        """Return all the interfaces provided by this module
        """
        return list(self.implements_directly)


class ZopeInterfaceClass(model.Class):
    isinterface = False
    isschemafield = False
    isinterfaceclass = False
    implementsOnly = False
    implementedby_directly = None # [objects], when isinterface == True
    def setup(self):
        super(ZopeInterfaceClass, self).setup()
        self.implements_directly = [] # [name of interface]

    @property
    def allImplementedInterfaces(self):
        """Return all the interfaces implemented by this class.

        This returns them in something like the classic class MRO.
        """
        r = list(self.implements_directly)
        if self.implementsOnly:
            return r
        for b in self.baseobjects:
            if b is None:
                continue
            for interface in b.allImplementedInterfaces:
                if interface not in r:
                    r.append(interface)
        return r

    @property
    def allImplementations(self):
        r = list(self.implementedby_directly)
        stack = list(r)
        while stack:
            c = stack.pop(0)
            for sc in c.subclasses:
                if sc.implementsOnly:
                    continue
                stack.append(sc)
                if sc not in r:
                    r.append(sc)
        return r


class ZopeInterfaceFunction(model.Function):
    def docsources(self):
        for source in super(ZopeInterfaceFunction, self).docsources():
            yield source
        if not isinstance(self.parent, (model.Class, model.Module)):
            return
        for interface in self.parent.allImplementedInterfaces:
            io = self.system.objForFullName(interface)
            if io is not None:
                for io2 in io.allbases(include_self=True):
                    if self.name in io2.contents:
                        yield io2.contents[self.name]

def addInterfaceInfoToScope(scope, interfaceargs):
    for arg in interfaceargs:
        if not isinstance(arg, tuple):
            fullName = scope.expandName(astor.to_source(arg).strip())
        else:
            fullName = arg[1]
        obj = scope.system.objForFullName(fullName)
        if isinstance(obj, ZopeInterfaceClass):
            scope.implements_directly.append(fullName)
            if not obj.isinterface:
                obj.system.msg(
                    'zopeinterface',
                    'probable interface %r not marked as such'%obj,
                    thresh=1)
                obj.isinterface = True
                obj.kind = "Interface"
                obj.implementedby_directly = []
            obj.implementedby_directly.append(scope)
        elif obj is not None:
            obj.system.msg(
                'zopeinterface',
                'probable interface %r not detected as class'%obj)

def addInterfaceInfoToModule(module, interfaceargs):
    addInterfaceInfoToScope(module, interfaceargs)

def addInterfaceInfoToClass(cls, interfaceargs, implementsOnly):
    cls.implementsOnly = implementsOnly
    if implementsOnly:
        cls.implements_directly = []
    addInterfaceInfoToScope(cls, interfaceargs)


schema_prog = re.compile(r'zope\.schema\.([a-zA-Z_][a-zA-Z0-9_]*)')
interface_prog = re.compile(
    r'zope\.schema\.interfaces\.([a-zA-Z_][a-zA-Z0-9_]*)'
    r'|zope\.interface\.Interface')

def namesInterface(system, name):
    if interface_prog.match(name):
        return True
    obj = system.objForFullName(name)
    if not obj or not isinstance(obj, model.Class):
        return False
    return obj.isinterface

def extractAttributeDescription(node):
    pass

def extractSchemaDescription(node):
    pass

def extractStringLiteral(node):
    if isinstance(node, ast.Str):
        return node.s
    elif isinstance(node, ast.Name):
        return node.id
    elif isinstance(node, ast.Call):
        return node.args[0].s
    else:
        raise TypeError("cannot extract string from %r" % (node,))

class ZopeInterfaceModuleVisitor(astbuilder.ModuleVistor):

    schema_like_patterns = [
        (r'zope\.interface\.Attribute', extractAttributeDescription),
        ]

    def funcNameFromCall(self, node):
        if isinstance(node.func, ast.Name):
            name = node.func.id
        elif isinstance(node.func, ast.Attribute):
            name = astor.to_source(node).strip().split("(")[0]
        elif isinstance(node.func, ast.Call):
            return self.funcNameFromCall(node.func)
        else:
            return None
        return self.builder.current.expandName(name)

    def _handleAssignmentInModule(self, target, annotation, expr, lineno):
        super(ZopeInterfaceModuleVisitor, self)._handleAssignmentInModule(
                target, annotation, expr, lineno)

        if not isinstance(expr, ast.Call):
            return
        funcName = self.funcNameFromCall(expr)
        if funcName is None:
            return
        ob = self.system.objForFullName(funcName)
        if isinstance(ob, model.Class) and ob.isinterfaceclass:
            interface = self.builder.pushClass(target, "...", lineno)
            self.builder.system.msg('parsing', 'new interface')
            interface.isinterface = True
            interface.implementedby_directly = []
            interface.linenumber = lineno
            self.builder.popClass()

    def _handleAssignmentInClass(self, target, annotation, expr, lineno):
        super(ZopeInterfaceModuleVisitor, self)._handleAssignmentInClass(
                target, annotation, expr, lineno)

        def handleSchemaField():
            descriptions = [arg.value for arg in expr.keywords if arg.arg == 'description']
            if len(descriptions) > 1:
                self.builder.system.msg('parsing', 'xxx')
            elif len(descriptions) == 1:
                attr.docstring = extractStringLiteral(descriptions[0])

        if not isinstance(expr, ast.Call):
            return
        attr = self.builder.current.contents[target]
        funcName = self.funcNameFromCall(expr)
        if funcName is None:
            return
        if funcName == 'zope.interface.Attribute':
            args = expr.args
            if args is not None and len(args) == 1:
                attr.kind = 'Attribute'
                attr.docstring = extractStringLiteral(expr.args[0])
        elif schema_prog.match(funcName):
            attr.kind = schema_prog.match(funcName).group(1)
            handleSchemaField()
        else:
            cls = self.builder.system.objForFullName(funcName)
            if isinstance(cls, ZopeInterfaceClass) and cls.isschemafield:
                attr.kind = cls.name
                handleSchemaField()

    def visit_Call(self, node):
        base = self.funcNameFromCall(node)
        if base is None:
            return
        meth = getattr(self, "visit_Call_" + base.replace('.', '_'), None)
        if meth is not None:
            meth(base, node)

    def visit_Call_zope_interface_moduleProvides(self, funcName, node):
        if not isinstance(self.builder.current, model.Module):
            self.default(node)
            return

        addInterfaceInfoToModule(self.builder.current, node.args)

    def visit_Call_zope_interface_implements(self, funcName, node):
        if not isinstance(self.builder.current, model.Class):
            self.default(node)
            return
        addInterfaceInfoToClass(self.builder.current, node.args,
                                funcName == 'zope.interface.implementsOnly')
    visit_Call_zope_interface_implementsOnly = visit_Call_zope_interface_implements

    def visit_Call_zope_interface_classImplements(self, funcName, node):
        clsname = self.builder.current.expandName(
            astor.to_source(node.args[0]).strip())
        if clsname not in self.system.allobjects:
            self.builder.system.msg(
                "parsing",
                "classImplements on unknown class %r"%clsname)
            return
        cls = self.system.allobjects[clsname]
        addInterfaceInfoToClass(cls, node.args[1:],
                                funcName == 'zope.interface.classImplementsOnly')
    visit_Call_zope_interface_classImplementsOnly = visit_Call_zope_interface_classImplements

    def visit_ClassDef(self, node):
        super(ZopeInterfaceModuleVisitor, self).visit_ClassDef(node)
        cls = self.builder.current.contents[node.name]

        bases = []

        for base in cls.bases:
            if isinstance(base, ast.Name):
                bases.append(self.builder.current.expandName(base.id))
            elif isinstance(base, text_type):
                bases.append(self.builder.current.expandName(base))
            else:
                raise Exception(base)

        if 'zope.interface.interface.InterfaceClass' in bases:
            cls.isinterfaceclass = True
            
        if len([b for b in cls.bases
                if namesInterface(self.system, b)]) > 0:
            cls.isinterface = True
            cls.kind = "Interface"
            cls.implementedby_directly = []

        for n, o in zip(cls.bases, cls.baseobjects):
            if schema_prog.match(n) or (o and o.isschemafield):
                cls.isschemafield = True

        for ((dn, fn, o), args) in cls.decorators:
            if fn == 'zope.interface.implementer':
                addInterfaceInfoToClass(cls, args, False)


class ZopeInterfaceASTBuilder(astbuilder.ASTBuilder):
    ModuleVistor = ZopeInterfaceModuleVisitor


class ZopeInterfaceSystem(model.System):
    Module = ZopeInterfaceModule
    Class = ZopeInterfaceClass
    Function = ZopeInterfaceFunction
    defaultBuilder = ZopeInterfaceASTBuilder