File: yaqlized.py

package info (click to toggle)
python-yaql 2.0.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,048 kB
  • sloc: python: 7,765; sh: 25; makefile: 19
file content (216 lines) | stat: -rw-r--r-- 6,550 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
#    Copyright (c) 2016 Mirantis, Inc.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
"""
Any Python class or object can be yaqlized. It is possible to call methods,
access attributes/properties and index of yaqlized objects.

The first way to yaqlize object is using function call:

.. code-block:: python

    class A(object):
        foo = 256
        def bar(self):
            print('yaqlization works with methods too')

    sample_object = A()
    yaqlization.yaqlize(sample_object)

The second way is using decorator:

.. code-block:: python

    @yaqlization.yaqlize
    class A(object):
        foo = 256
        def bar(self):
            print('yaqlization works with methods too')

Any mentioned operation on yaqlized objects can be disabled with additional
parameters for yaqlization. Also it is possible to specify whitelist/blacklist
of methods/attributes/keys that are exposed to the yaql.

This module provides implemented operators on Yaqlized objects.
"""


import re

from yaql.language import expressions
from yaql.language import runner
from yaql.language import specs
from yaql.language import utils
from yaql.language import yaqltypes
from yaql import yaqlization


REGEX_TYPE = type(re.compile('.'))


class Yaqlized(yaqltypes.GenericType):
    def __init__(self, can_access_attributes=False, can_call_methods=False,
                 can_index=False):
        def check_value(value, context, *args, **kwargs):
            settings = yaqlization.get_yaqlization_settings(value)
            if settings is None:
                return False
            if can_access_attributes and not settings['yaqlizeAttributes']:
                return False
            if can_call_methods and not settings['yaqlizeMethods']:
                return False
            if can_index and not settings['yaqlizeIndexer']:
                return False
            return True

        super(Yaqlized, self).__init__(checker=check_value, nullable=False)


def _match_name_to_entry(name, entry):
    if name == entry:
        return True
    elif isinstance(entry, REGEX_TYPE):
        return entry.search(name) is not None
    elif callable(entry):
        return entry(name)
    return False


def _validate_name(name, settings, exception_cls=AttributeError):
    if name.startswith('_'):
        raise exception_cls('Cannot access ' + name)
    whitelist = settings['whitelist']
    if whitelist:
        for entry in whitelist:
            if _match_name_to_entry(name, entry):
                return
        raise exception_cls('Cannot access ' + name)
    blacklist = settings['blacklist']
    if blacklist:
        for entry in blacklist:
            if _match_name_to_entry(name, entry):
                raise exception_cls('Cannot access ' + name)


def _remap_name(name, settings):
    return settings['attributeRemapping'].get(name, name)


def _auto_yaqlize(value, settings):
    if not settings['autoYaqlizeResult']:
        return
    if isinstance(value, type):
        cls = value
    else:
        cls = type(value)
    if cls.__module__ == int.__module__:
        return
    try:
        yaqlization.yaqlize(value, auto_yaqlize_result=True)
    except Exception:
        pass


@specs.parameter('receiver', Yaqlized(can_call_methods=True))
@specs.parameter('expr', yaqltypes.YaqlExpression(expressions.Function))
@specs.name('#operator_.')
def op_dot(receiver, expr, context, engine):
    """:yaql:operator .

    Evaluates expression on receiver and returns its result.

    :signature: receiver.expr
    :arg receiver: yaqlized receiver
    :argType receiver: yaqlized object, initialized with
        yaqlize_methods equal to True
    :arg expr: expression to be evaluated
    :argType expr: expression
    :returnType: any (expression return type)
    """
    settings = yaqlization.get_yaqlization_settings(receiver)
    mappings = _remap_name(expr.name, settings)

    _validate_name(expr.name, settings)
    if not isinstance(mappings, str):
        name = mappings[0]
        if len(mappings) > 0:
            arg_mappings = mappings[1]
        else:
            arg_mappings = {}
    else:
        name = mappings
        arg_mappings = {}

    func = getattr(receiver, name)
    args, kwargs = runner.translate_args(False, expr.args, {})
    args = tuple(arg(utils.NO_VALUE, context, engine) for arg in args)
    for key, value in kwargs.items():
        kwargs[arg_mappings.get(key, key)] = value(
            utils.NO_VALUE, context, engine)
    res = func(*args, **kwargs)
    _auto_yaqlize(res, settings)
    return res


@specs.parameter('obj', Yaqlized(can_access_attributes=True))
@specs.parameter('attr', yaqltypes.Keyword())
@specs.name('#operator_.')
def attribution(obj, attr):
    """:yaql:operator .

    Returns attribute of the object.

    :signature: obj.attr
    :arg obj: yaqlized object
    :argType obj: yaqlized object, initialized with
        yaqlize_attributes equal to True
    :arg attr: attribute name
    :argType attr: keyword
    :returnType: any
    """
    settings = yaqlization.get_yaqlization_settings(obj)
    _validate_name(attr, settings)
    attr = _remap_name(attr, settings)
    res = getattr(obj, attr)
    _auto_yaqlize(res, settings)
    return res


@specs.parameter('obj', Yaqlized(can_index=True))
@specs.name('#indexer')
def indexation(obj, key):
    """:yaql:operator indexer

    Returns value of attribute/property key of the object.

    :signature: obj[key]
    :arg obj: yaqlized object
    :argType obj: yaqlized object, initialized with
        yaqlize_indexer equal to True
    :arg key: index name
    :argType key: keyword
    :returnType: any
    """
    settings = yaqlization.get_yaqlization_settings(obj)
    _validate_name(key, settings, KeyError)
    res = obj[key]
    _auto_yaqlize(res, settings)
    return res


def register(context):
    context = context.create_child_context()
    context.register_function(op_dot)
    context.register_function(attribution)
    context.register_function(indexation)
    return context