File: annotations.py

package info (click to toggle)
sip6 6.13.0-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 13,368 kB
  • sloc: ansic: 175,840; python: 19,386; makefile: 25; cpp: 8
file content (273 lines) | stat: -rw-r--r-- 8,726 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
# SPDX-License-Identifier: BSD-2-Clause

# Copyright (c) 2025 Phil Thompson <phil@riverbankcomputing.com>


class DottedName(str):
    """ Encapsulate a dotted name.  A dedicated type is used (rather than a
    str) because we need to be able to distinguish it from a quoted string when
    used as the value of an annotation.
    """

    pass


class InvalidAnnotation(Exception):
    """ An invalid annotation. """

    def __init__(self, name, message, use):
        """ Initialise the exception. """

        self._text = "{0} {1}".format(name, message)

        # The value to use for the annotation.
        self.use = use

    def __str__(self):
        """ Return the exception as a user friendly string. """

        return self._text


class RequiredAnnotation(InvalidAnnotation):
    """ A required annotation. """

    def __init__(name, use):
        """ Initialise the exception. """

        super().__init__(name, "requires a value", use=use)


def validate_annotation_value(pm, p, symbol, name, value):
    """ Return a valid value for the annotation or raise an InvalidAnnotation
    exception.
    """

    try:
        validator = _ANNOTATION_TYPES[name]
    except KeyError:
        raise InvalidAnnotation(name, "is not a known annotation", use=None)

    return validator(pm, p, symbol, name, value)


def bind(validator, **proto_kw):
    """ Return a function that when called with a validator function and
    prototype keyword arguments will itself return a function that will create
    an annotation-specific validator.
    """

    # This takes the prototype validator-specific keyword arguments and returns
    # a function that will itself create a validator with annotation-specific
    # arguments based on the prototypes.
    def proto_validator(**bound_kw):
        # Create the annotation specific keyword arguments by taking the
        # prototypes and updating them with the ones bound to the specific
        # annotation.
        kw = proto_kw.copy()
        kw.update(bound_kw)

        # This takes the name and value of the annotation and calls the
        # validator along with the annotation-specific keyword arguments.
        def bound_validator(pm, p, symbol, name, value):
            return validator(pm, p, symbol, name, value, **kw)

        return bound_validator

    return proto_validator


def validate_boolean(pm, p, symbol, name, value):
    """ Return a valid boolean value. """

    if value is None:
        return True

    raise InvalidAnnotation(name, "must not have a value", use=False)

boolean = bind(validate_boolean)


def validate_integer(pm, p, symbol, name, value, *, optional):
    """ Return a valid, possibly optional, integer. """

    if value is None:
        if optional:
            return None

        raise RequiredAnnotation(name, use=0)

    if not isinstance(value, int):
        raise InvalidAnnotation(name, "must be an integer", use=0)

    return value

integer = bind(validate_integer, optional=False)


def validate_name(pm, p, symbol, name, value, *, allow_dots, optional):
    """ Return a valid, possibly optional, possibly dotted name. """

    if value is None:
        if optional:
            return ''

        raise RequiredAnnotation(name, use='')

    if not isinstance(value, DottedName):
        raise InvalidAnnotation(name, "must be an unquoted name", use='')

    if '.' in value and not allow_dots:
        raise InvalidAnnotation(name, "cannot contain '.'", use='')

    return value

name = bind(validate_name, allow_dots=False, optional=False)


def validate_string(pm, p, symbol, name, value, optional):
    """ Return a valid, possibly optional, string value. """

    if value is None:
        if optional:
            return ''

    if not isinstance(value, str):
        raise InvalidAnnotation(name, "must be a quoted string", use='')

    # Handle any embedded selectors.
    fields = []

    # Break into a list of ';' separated fields allowing for escaped ';'s.
    field = ''
    needs_appending = True
    for ch in value:
        if ch == ';':
            if field and field[-1] == '\\':
                # Remove the escape.
                field = field[:-1]
            else:
                fields.append(field.strip())
                field = ''
                needs_appending = False
                continue

        field += ch
        needs_appending = True

    if needs_appending:
        fields.append(field.strip())

    # Go through each field looking for ':' separated selector/value pairs.
    for fields in fields:
        # A missing selector is treated as 'true'.
        parts = field.split(':', maxsplit=1)
        if len(parts) != 2:
            return field

        selector, field_value = parts

        # An escaped ':' means the selector is missing.
        if selector and selector[-1] == '\\':
            # Remove the escape.
            return field.replace('\\', '', 1)

        # See if the selector is inverted.
        if selector.startswith('!'):
            selector = selector[1:]
            inverted = True
        else:
            inverted = False

        if pm.evaluate_feature_or_platform(p, symbol, selector, inverted):
            return field_value.strip()

    # No value was selected so ignore the annotation completely.
    return None

string = bind(validate_string, optional=False)


def validate_string_list(pm, p, symbol, name, value):
    """ Return a valid string list value. """

    if not isinstance(value, str):
        raise InvalidAnnotation(name, "must be a quoted string", use=[])

    return value.split(' ')

string_list = bind(validate_string_list)


# The annotations and the type of their values.
_ANNOTATION_TYPES = {
    '__imatmul__':              boolean(),
    '__len__':                  boolean(),
    '__matmul__':               boolean(),
    'AbortOnException':         boolean(),
    'Abstract':                 boolean(),
    'AllowNone':                boolean(),
    'Array':                    boolean(),
    'ArraySize':                boolean(),
    'AutoGen':                  name(optional=True),
    'BaseType':                 name(),
    'Capsule':                  boolean(),
    'Constrained':              boolean(),
    'Deprecated':               string(optional=True),
    'Default':                  boolean(),
    'DelayDtor':                boolean(),
    'DisallowNone':             boolean(),
    'ExportDerived':            boolean(),
    'ExportDerivedLocally':     boolean(),
    'External':                 boolean(),
    'Encoding':                 string(),
    'Factory':                  boolean(),
    'FileExtension':            string(),
    'GetWrapper':               boolean(),
    'HoldGIL':                  boolean(),
    'In':                       boolean(),
    'KeepReference':            integer(optional=True),
    'KeywordArgs':              string(),
    'Metatype':                 name(allow_dots=True),
    'Mixin':                    boolean(),
    'Movable':                  boolean(),
    'NewThread':                boolean(),
    'NoArgParser':              boolean(),
    'NoAssignmentOperator':     boolean(),
    'NoCopy':                   boolean(),
    'NoCopyCtor':               boolean(),
    'NoDefaultCtor':            boolean(),
    'NoDefaultCtors':           boolean(),
    'NoDerived':                boolean(),
    'NoRaisesPyException':      boolean(),
    'NoRelease':                boolean(),
    'NoScope':                  boolean(),
    'NoSetter':                 boolean(),
    'NoTypeHint':               boolean(),
    'NoTypeName':               boolean(),
    'NoVirtualErrorHandler':    boolean(),
    'Numeric':                  boolean(),
    'Out':                      boolean(),
    'PostHook':                 name(),
    'PreHook':                  name(),
    'PyInt':                    boolean(),
    'PyName':                   name(),
    'PyQtFlags':                integer(),
    'PyQtFlagsEnums':           string_list(),
    'PyQtInterface':            string(),
    'PyQtNoQMetaObject':        boolean(),
    'RaisesPyException':        boolean(),
    'ReleaseGIL':               boolean(),
    'ResultSize':               boolean(),
    'ScopesStripped':           integer(),
    'Sequence':                 boolean(),
    'Supertype':                name(allow_dots=True),
    'Transfer':                 boolean(),
    'TransferBack':             boolean(),
    'TransferThis':             boolean(),
    'TypeHint':                 string(),
    'TypeHintIn':               string(),
    'TypeHintOut':              string(),
    'TypeHintValue':            string(),
    'VirtualErrorHandler':      name(),
}