Stan Lagun 201eb75ae4 Ability to reference single object several times
This commit introduces ref(templateName [, parameterName] [, idOnly])
YAQL function was added to UI definition DSL. This function evaluates
template templateName and fixes the result in parameters under
parameterName key (or templateName if the second parameter was omitted).
Then it generates object ID and places it into ?/id field. On the first
use of parameterName or if idOnly is false the function will return
the whole object structure. On subsequent calls or if idOnly is true it
will return the ID that was generated upon the first call.
Thus the function brings ability to reference single object several
times.

ID generation is done in a safe way with a special temporary type that
wraps string ID and replaced with string representation upon
serialization. This way it is still impossible for a developer to
explicitly provide ID value in the template (it is still going to be
overwritten because it is not of an ObjectID type and it is impossible
to create instance of ObjectID from yaql expression)

Change-Id: Idf114113a73d00ab2132bd085157ca01e94dae3e
2016-11-10 23:30:30 +00:00

163 lines
4.5 KiB
Python

# Copyright (c) 2013 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 re
import string
import types
import uuid
import six
from django.core import validators
_LOCALIZABLE_KEYS = set(['label', 'help_text', 'error_messages'])
class ObjectID(object):
def __init__(self):
self.object_id = str(uuid.uuid4())
def is_localizable(keys):
return set(keys).intersection(_LOCALIZABLE_KEYS)
def camelize(name):
"""Turns snake_case name into SnakeCase."""
return ''.join([bit.capitalize() for bit in name.split('_')])
def decamelize(name):
"""Turns CamelCase/camelCase name into camel_case."""
pat = re.compile(r'([A-Z]*[^A-Z]*)(.*)')
bits = []
while True:
head, tail = re.match(pat, name).groups()
bits.append(head)
if tail:
name = tail
else:
break
return '_'.join([bit.lower() for bit in bits])
def explode(_string):
"""Explodes a string into a list of one-character strings."""
if not _string or not isinstance(_string, six.string_types):
return _string
else:
return list(_string)
def prepare_regexp(regexp):
"""Converts regular expression string pattern into RegexValidator object.
Also /regexp/flags syntax is allowed, where flags is a string of
one-character flags that will be appended to the compiled regexp.
"""
if regexp.startswith('/'):
groups = re.match(r'^/(.*)/([A-Za-z]*)$', regexp).groups()
regexp, flags_str = groups
flags = 0
for flag in explode(flags_str):
flag = flag.upper()
if hasattr(re, flag):
flags |= getattr(re, flag)
return validators.RegexValidator(re.compile(regexp, flags))
else:
return validators.RegexValidator(re.compile(regexp))
def recursive_apply(predicate, transformer, value, *args):
def rec(val):
if predicate(val, *args):
return rec(transformer(val, *args))
elif isinstance(val, dict):
return dict((rec(k), rec(v)) for (k, v) in six.iteritems(val))
elif isinstance(val, list):
return [rec(v) for v in val]
elif isinstance(val, tuple):
return tuple([rec(v) for v in val])
elif isinstance(val, types.GeneratorType):
return rec(val)
else:
return val
return rec(value)
def evaluate(value, context):
return recursive_apply(
lambda v, _ctx: hasattr(v, 'evaluate'),
lambda v, _ctx: v.evaluate(context=_ctx),
value, context)
def insert_hidden_ids(application):
def wrap(k, v):
if k == '?' and isinstance(v, dict) and not isinstance(
v.get('id'), ObjectID):
v['id'] = str(uuid.uuid4())
return k, v
elif isinstance(v, ObjectID):
return k, v.object_id
else:
return rec(k), rec(v)
def rec(val):
if isinstance(val, dict):
return dict(wrap(k, v) for k, v in six.iteritems(val))
elif isinstance(val, list):
return [rec(v) for v in val]
else:
return val
return rec(application)
def int2base(x, base):
"""Converts decimal integers to another number base from base-2 to base-36
:param x: decimal integer
:param base: number base, max value is 36
:return: integer converted to the specified base
"""
digs = string.digits + string.ascii_lowercase
if x < 0:
sign = -1
elif x == 0:
return '0'
else:
sign = 1
x *= sign
digits = []
while x:
digits.append(digs[x % base])
x //= base
if sign < 0:
digits.append('-')
digits.reverse()
return ''.join(digits)
def to_str(text):
if not isinstance(text, str):
# unicode in python2
if isinstance(text, six.text_type):
text = text.encode('utf-8')
# bytes in python3
elif isinstance(text, six.binary_type):
text = text.decode('utf-8')
return text