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 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
|
# Copyright (c) 2015 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.
import inspect
from yaql.language import exceptions
from yaql.language import utils
from yaql.language import yaqltypes
NO_DEFAULT = utils.create_marker('<NoValue>')
class ParameterDefinition(object):
__slots__ = ('value_type', 'name', 'position', 'default', 'alias')
def __init__(self, name, value_type=None, position=None, alias=None,
default=None):
self.value_type = value_type
self.name = name
self.position = position
self.default = default
self.alias = alias
def __repr__(self):
return '{0} => position={1} value_type={2} default={3}'.format(
self.name, self.position, self.value_type, self.default)
def clone(self):
return ParameterDefinition(self.name, self.value_type,
self.position, self.alias, self.default)
class FunctionDefinition(object):
__slots__ = ('is_method', 'is_function', 'name', 'parameters', 'payload',
'doc', 'no_kwargs', 'meta')
def __init__(self, name, payload, parameters=None, doc='', meta=None,
is_function=True, is_method=False, no_kwargs=False):
self.is_method = is_method
self.is_function = is_function
self.name = name
self.parameters = {} if not parameters else parameters
self.payload = payload
self.doc = doc
self.no_kwargs = no_kwargs
self.meta = meta or {}
def __call__(self, engine, context, receiver=utils.NO_VALUE):
def func(*args, **kwargs):
if receiver is not utils.NO_VALUE:
args = (receiver,) + args
return self.get_delegate(receiver, engine, context, args, kwargs)()
return func
def clone(self):
parameters = {key: p.clone() for key, p in self.parameters.items()}
res = FunctionDefinition(
self.name, self.payload, parameters, self.doc,
self.meta, self.is_function, self.is_method, self.no_kwargs)
return res
def strip_hidden_parameters(self):
fd = self.clone()
keys_to_remove = set()
for k, v in fd.parameters.items():
if not isinstance(v.value_type, yaqltypes.HiddenParameterType):
continue
keys_to_remove.add(k)
if v.position is not None:
for v2 in fd.parameters.values():
if v2.position is not None and v2.position > v.position:
v2.position -= 1
for key in keys_to_remove:
del fd.parameters[key]
return fd
def set_parameter(self, name, value_type=None, nullable=None,
alias=None, overwrite=False):
if isinstance(name, ParameterDefinition):
if name.name in self.parameters and not overwrite:
raise exceptions.DuplicateParameterDecoratorException(
function_name=self.name or self.payload.__name__,
param_name=name.name)
self.parameters[name.name] = name
return name
spec = inspect.getfullargspec(self.payload)
if isinstance(name, int):
if 0 <= name < len(spec.args):
name = spec.args[name]
elif name == len(spec.args) and spec.varargs is not None:
name = spec.varargs
else:
raise IndexError('argument position is out of range')
arg_name = name
if name == spec.varkw:
position = None
arg_name = '**'
elif name == spec.varargs:
position = len(spec.args)
arg_name = '*'
elif name in spec.kwonlyargs:
position = None
elif name not in spec.args:
raise exceptions.NoParameterFoundException(
function_name=self.name or self.payload.__name__,
param_name=name)
else:
position = spec.args.index(name)
default = NO_DEFAULT
if spec.defaults is not None and name in spec.args:
index = spec.args.index(name) - len(spec.args)
if index >= -len(spec.defaults):
default = spec.defaults[index]
elif spec.kwonlydefaults is not None:
default = spec.kwonlydefaults.get(name, NO_DEFAULT)
if arg_name in self.parameters and not overwrite:
raise exceptions.DuplicateParameterDecoratorException(
function_name=self.name or self.payload.__name__,
param_name=name)
yaql_type = value_type
p_nullable = nullable
if value_type is None:
if p_nullable is None:
p_nullable = True
base_type = object \
if default in (None, NO_DEFAULT, utils.NO_VALUE) \
else type(default)
yaql_type = yaqltypes.PythonType(base_type, p_nullable)
elif not isinstance(value_type, yaqltypes.SmartType):
if p_nullable is None:
p_nullable = default is None
yaql_type = yaqltypes.PythonType(value_type, p_nullable)
pd = ParameterDefinition(
name, yaql_type, position, alias, default
)
self.parameters[arg_name] = pd
return pd
def insert_parameter(self, name, value_type=None, nullable=None,
alias=None, overwrite=False):
pd = self.set_parameter(name, value_type, nullable, alias, overwrite)
for p in self.parameters.values():
if p is pd:
continue
if p.position is not None and p.position >= pd.position:
p.position += 1
def map_args(self, args, kwargs, context, engine):
kwargs = dict(kwargs)
positional_args = len(args) * [
self.parameters.get('*', utils.NO_VALUE)]
max_dst_positional_args = len(args) + len(self.parameters)
positional_fix_table = max_dst_positional_args * [0]
keyword_args = {}
for p in self.parameters.values():
if p.position is not None and isinstance(
p.value_type, yaqltypes.HiddenParameterType):
for index in range(p.position + 1, len(positional_fix_table)):
positional_fix_table[index] += 1
for key, p in self.parameters.items():
arg_name = p.alias or p.name
if p.position is not None and key != '*':
arg_position = p.position - positional_fix_table[p.position]
if isinstance(p.value_type, yaqltypes.HiddenParameterType):
continue
elif arg_position < len(args) and args[arg_position] \
is not utils.NO_VALUE:
if arg_name in kwargs:
return None
positional_args[arg_position] = p
elif arg_name in kwargs:
keyword_args[arg_name] = p
del kwargs[arg_name]
elif p.default is NO_DEFAULT:
return None
elif arg_position < len(args) and args[arg_position]:
positional_args[arg_position] = p
elif p.position is None and key != '**':
if isinstance(p.value_type, yaqltypes.HiddenParameterType):
continue
elif arg_name in kwargs:
keyword_args[arg_name] = p
del kwargs[arg_name]
elif p.default is NO_DEFAULT:
return None
if len(kwargs) > 0:
if '**' in self.parameters:
argdef = self.parameters['**']
for key in kwargs:
keyword_args[key] = argdef
else:
return None
for i in range(len(positional_args)):
if positional_args[i] is utils.NO_VALUE:
return None
value = args[i]
if value is utils.NO_VALUE:
value = positional_args[i].default
if not positional_args[i].value_type.check(value, context, engine):
return None
for kwd in kwargs:
if not keyword_args[kwd].value_type.check(
kwargs[kwd], context, engine):
return None
return tuple(positional_args), keyword_args
def get_delegate(self, receiver, engine, context, args, kwargs):
def checked(val, param):
if not param.value_type.check(val, context, engine):
raise exceptions.ArgumentException(param.name)
def convert_arg_func(context2):
try:
return param.value_type.convert(
val, receiver, context2, self, engine)
except exceptions.ArgumentValueException:
raise exceptions.ArgumentException(param.name)
return convert_arg_func
kwargs = kwargs.copy()
kwargs = dict(kwargs)
positional = 0
for arg_name, p in self.parameters.items():
if p.position is not None and arg_name != '*':
positional += 1
positional_args = positional * [None]
positional_fix_table = positional * [0]
keyword_args = {}
for p in self.parameters.values():
if p.position is not None and isinstance(
p.value_type, yaqltypes.HiddenParameterType):
for index in range(p.position + 1, positional):
positional_fix_table[index] += 1
for key, p in self.parameters.items():
arg_name = p.alias or p.name
if p.position is not None and key != '*':
if isinstance(p.value_type, yaqltypes.HiddenParameterType):
positional_args[p.position] = checked(None, p)
positional -= 1
elif p.position - positional_fix_table[p.position] < len(
args) and args[p.position - positional_fix_table[
p.position]] is not utils.NO_VALUE:
if arg_name in kwargs:
raise exceptions.ArgumentException(p.name)
positional_args[p.position] = checked(
args[p.position - positional_fix_table[
p.position]], p)
elif arg_name in kwargs:
positional_args[p.position] = checked(
kwargs.pop(arg_name), p)
elif p.default is not NO_DEFAULT:
positional_args[p.position] = checked(p.default, p)
else:
raise exceptions.ArgumentException(p.name)
elif p.position is None and key != '**':
if isinstance(p.value_type, yaqltypes.HiddenParameterType):
keyword_args[key] = checked(None, p)
elif arg_name in kwargs:
keyword_args[key] = checked(kwargs.pop(arg_name), p)
elif p.default is not NO_DEFAULT:
keyword_args[key] = checked(p.default, p)
else:
raise exceptions.ArgumentException(p.name)
if len(args) > positional:
if '*' in self.parameters:
argdef = self.parameters['*']
positional_args.extend(
map(lambda t: checked(t, argdef), args[positional:]))
else:
raise exceptions.ArgumentException('*')
if len(kwargs) > 0:
if '**' in self.parameters:
argdef = self.parameters['**']
for key, value in kwargs.items():
keyword_args[key] = checked(value, argdef)
else:
raise exceptions.ArgumentException('**')
def func():
new_context = context.create_child_context()
result = self.payload(
*tuple(map(lambda t: t(new_context),
positional_args)),
**dict(map(lambda t: (t[0], t[1](new_context)),
keyword_args.items()))
)
return result
return func
def is_valid_method(self):
min_position = len(self.parameters)
min_arg = None
for p in self.parameters.values():
if p.position is not None and p.position < min_position and \
not isinstance(p.value_type,
yaqltypes.HiddenParameterType):
min_position = p.position
min_arg = p
return min_arg and not isinstance(
min_arg.value_type, yaqltypes.LazyParameterType)
def _get_function_definition(func):
if not hasattr(func, '__yaql_function__'):
fd = FunctionDefinition(None, func, {}, func.__doc__)
func.__yaql_function__ = fd
return func.__yaql_function__
def _infer_parameter_type(name):
if name == 'context' or name == '__context':
return yaqltypes.Context()
elif name == 'engine' or name == '__engine':
return yaqltypes.Engine()
elif name == 'yaql_interface' or name == '__yaql_interface':
return yaqltypes.YaqlInterface()
def convert_function_name(function_name, convention):
if not function_name:
return function_name
function_name = function_name.rstrip('_')
if not convention:
return function_name
if not function_name[0].isalpha():
finish = function_name.find(function_name[0], 1)
if finish <= 1:
return function_name
return function_name[:finish + 1] + convention.convert_function_name(
function_name[finish + 1:])
return convention.convert_function_name(function_name)
def convert_parameter_name(parameter_name, convention):
if not parameter_name:
return parameter_name
parameter_name = parameter_name.rstrip('_')
if not convention:
return parameter_name
return convention.convert_parameter_name(parameter_name)
def get_function_definition(func, name=None, function=None, method=None,
convention=None, parameter_type_func=None):
if parameter_type_func is None:
parameter_type_func = _infer_parameter_type
fd = _get_function_definition(func).clone()
spec = inspect.getfullargspec(func)
for arg in spec.args + spec.kwonlyargs:
if arg not in fd.parameters:
fd.set_parameter(arg, parameter_type_func(arg))
if spec.varargs and '*' not in fd.parameters:
fd.set_parameter(spec.varargs, parameter_type_func(spec.varargs))
if spec.varkw and '**' not in fd.parameters:
fd.set_parameter(spec.varkw, parameter_type_func(spec.varkw))
if name is not None:
fd.name = name
elif fd.name is None:
fd.name = convert_function_name(fd.payload.__name__, convention)
elif convention is not None:
fd.name = convert_function_name(fd.name, convention)
if function is not None:
fd.is_function = function
if method is not None:
fd.is_method = method
if convention:
for p in fd.parameters.values():
if p.alias is None:
p.alias = convert_parameter_name(p.name, convention)
return fd
def _parameter(name, value_type=None, nullable=None, alias=None):
def wrapper(func):
fd = _get_function_definition(func)
fd.set_parameter(name, value_type, nullable, alias)
return func
return wrapper
def parameter(name, value_type=None, nullable=None, alias=None):
if value_type is not None and isinstance(
value_type, yaqltypes.HiddenParameterType):
raise ValueError('Use inject() for hidden parameters')
return _parameter(name, value_type, nullable=nullable, alias=alias)
def inject(name, value_type=None, nullable=None, alias=None):
if value_type is not None and not isinstance(
value_type, yaqltypes.HiddenParameterType):
raise ValueError('Use parameter() for normal function parameters')
return _parameter(name, value_type, nullable=nullable, alias=alias)
def name(function_name):
def wrapper(func):
fd = _get_function_definition(func)
fd.name = function_name
return func
return wrapper
def method(func):
fd = _get_function_definition(func)
fd.is_method = True
fd.is_function = False
return func
def extension_method(func):
fd = _get_function_definition(func)
fd.is_method = True
fd.is_function = True
return func
def no_kwargs(func):
fd = _get_function_definition(func)
fd.no_kwargs = True
return func
def meta(name, value):
def wrapper(func):
fd = _get_function_definition(func)
fd.meta[name] = value
return func
return wrapper
def yaql_property(source_type):
def decorator(func):
@name('#property#{0}'.format(get_function_definition(func).name))
@parameter('obj', source_type)
def wrapper(obj):
return func(obj)
return wrapper
return decorator
|