template() contract function was introduced
template() works similar to the class() contract in regards to the data validation but do not instantiate objects. Instead the data is left in the object model dictionary format so that it could be instantiated later with the new() function. In addition it allows to excluse specified properties from validation and result template so that they could be provided later. Objects that are assigned to the property or argument with template() contract will be automatically converted to their object model representation. Change-Id: Id1016ae0cab1a18663900b27943f150d008e61f0
This commit is contained in:
parent
30846bb339
commit
23a67181eb
@ -14,6 +14,7 @@
|
||||
|
||||
import semantic_version
|
||||
|
||||
|
||||
EXPRESSION_MEMORY_QUOTA = 512 * 1024
|
||||
ITERATORS_LIMIT = 2000
|
||||
|
||||
@ -49,6 +50,9 @@ TL_CONTEXT = '__murano_context'
|
||||
TL_ID = '__thread_id'
|
||||
TL_OBJECT_STORE = '__murano_object_store'
|
||||
TL_SESSION = '__murano_execution_session'
|
||||
TL_CONTRACT_PASSKEY = '__murano_contract_passkey'
|
||||
TL_OBJECTS_DRY_RUN = '__murano_objects_dry_run'
|
||||
|
||||
|
||||
RUNTIME_VERSION_1_0 = semantic_version.Version('1.0.0')
|
||||
RUNTIME_VERSION_1_1 = semantic_version.Version('1.1.0')
|
||||
|
@ -71,6 +71,13 @@ class MethodArgumentUsages(object):
|
||||
All = {Standard, VarArgs, KwArgs}
|
||||
|
||||
|
||||
class DumpTypes(object):
|
||||
Serializable = 'Serializable'
|
||||
Inline = 'Inline'
|
||||
Mixed = 'Mixed'
|
||||
All = {Serializable, Inline, Mixed}
|
||||
|
||||
|
||||
class MuranoType(object):
|
||||
pass
|
||||
|
||||
|
@ -223,6 +223,16 @@ def get_class(name, context=None):
|
||||
return murano_type.package.find_class(name)
|
||||
|
||||
|
||||
def get_contract_passkey():
|
||||
current_thread = eventlet.greenthread.getcurrent()
|
||||
return getattr(current_thread, constants.TL_CONTRACT_PASSKEY, None)
|
||||
|
||||
|
||||
def is_objects_dry_run_mode():
|
||||
current_thread = eventlet.greenthread.getcurrent()
|
||||
return bool(getattr(current_thread, constants.TL_OBJECTS_DRY_RUN, False))
|
||||
|
||||
|
||||
def get_current_thread_id():
|
||||
global _threads_sequencer
|
||||
|
||||
@ -562,6 +572,34 @@ def parse_object_definition(spec, scope_type, context):
|
||||
}
|
||||
|
||||
|
||||
def assemble_object_definition(parsed, model_format=dsl_types.DumpTypes.Mixed):
|
||||
if model_format == dsl_types.DumpTypes.Inline:
|
||||
result = {
|
||||
parsed['type']: parsed['properties'],
|
||||
'id': parsed['id'],
|
||||
'name': parsed['name']
|
||||
}
|
||||
result.update(parsed['extra'])
|
||||
return result
|
||||
result = parsed['properties']
|
||||
header = {
|
||||
'id': parsed['id'],
|
||||
'name': parsed['name']
|
||||
}
|
||||
header.update(parsed['extra'])
|
||||
result['?'] = header
|
||||
if model_format == dsl_types.DumpTypes.Mixed:
|
||||
header['type'] = parsed['type']
|
||||
return result
|
||||
elif model_format == dsl_types.DumpTypes.Serializable:
|
||||
cls = parsed['type']
|
||||
if cls:
|
||||
header['type'] = format_type_string(cls)
|
||||
return result
|
||||
else:
|
||||
raise ValueError('Invalid Serialization Type')
|
||||
|
||||
|
||||
def function(c):
|
||||
if hasattr(c, 'im_func'):
|
||||
return c.im_func
|
||||
@ -606,3 +644,17 @@ def format_type_string(type_obj):
|
||||
type_obj.name, type_obj.version, type_obj.package.name)
|
||||
else:
|
||||
raise ValueError('Invalid argument')
|
||||
|
||||
|
||||
def patch_dict(dct, path, value):
|
||||
parts = path.split('.')
|
||||
for i in range(len(parts) - 1):
|
||||
if not isinstance(dct, dict):
|
||||
dct = None
|
||||
break
|
||||
dct = dct.get(parts[i])
|
||||
if isinstance(dct, dict):
|
||||
if value is yaqlutils.NO_VALUE:
|
||||
dct.pop(parts[-1])
|
||||
else:
|
||||
dct[parts[-1]] = value
|
||||
|
@ -19,7 +19,6 @@ from murano.dsl import dsl
|
||||
from murano.dsl import dsl_types
|
||||
from murano.dsl import exceptions
|
||||
from murano.dsl import helpers
|
||||
from murano.dsl import serializer
|
||||
from murano.dsl import yaql_integration
|
||||
|
||||
|
||||
@ -143,7 +142,8 @@ class MuranoObject(dsl_types.MuranoObject):
|
||||
init.invoke(self.real_this, (), init_args,
|
||||
context.create_child_context())
|
||||
|
||||
if not object_store.initializing and init:
|
||||
if (not object_store.initializing and init
|
||||
and not helpers.is_objects_dry_run_mode()):
|
||||
yield run_init
|
||||
|
||||
@property
|
||||
@ -254,13 +254,13 @@ class MuranoObject(dsl_types.MuranoObject):
|
||||
self.type.name, self.type.version, self.object_id, id(self))
|
||||
|
||||
def to_dictionary(self, include_hidden=False,
|
||||
serialization_type=serializer.DumpTypes.Serializable,
|
||||
serialization_type=dsl_types.DumpTypes.Serializable,
|
||||
allow_refs=False):
|
||||
context = helpers.get_context()
|
||||
result = {}
|
||||
for parent in self.__parents.values():
|
||||
result.update(parent.to_dictionary(
|
||||
include_hidden, serializer.DumpTypes.Serializable,
|
||||
include_hidden, dsl_types.DumpTypes.Serializable,
|
||||
allow_refs))
|
||||
for property_name in self.type.properties:
|
||||
if property_name in self.__properties:
|
||||
@ -276,14 +276,14 @@ class MuranoObject(dsl_types.MuranoObject):
|
||||
'as', context) == 'reference':
|
||||
prop_value = prop_value.object_id
|
||||
result[property_name] = prop_value
|
||||
if serialization_type == serializer.DumpTypes.Inline:
|
||||
if serialization_type == dsl_types.DumpTypes.Inline:
|
||||
result.pop('?')
|
||||
result = {
|
||||
self.type: result,
|
||||
'id': self.object_id,
|
||||
'name': self.name
|
||||
}
|
||||
elif serialization_type == serializer.DumpTypes.Mixed:
|
||||
elif serialization_type == dsl_types.DumpTypes.Mixed:
|
||||
result.update({'?': {
|
||||
'type': self.type,
|
||||
'id': self.object_id,
|
||||
|
@ -58,7 +58,8 @@ class ObjectStore(object):
|
||||
scope_type=None, context=None, keep_ids=False):
|
||||
# do the object model load in a temporary object store and copy
|
||||
# loaded objects here after that
|
||||
model_store = InitializationObjectStore(owner, self, keep_ids)
|
||||
model_store = InitializationObjectStore(
|
||||
owner, self, keep_ids)
|
||||
with helpers.with_object_store(model_store):
|
||||
result = model_store.load(
|
||||
value, owner, scope_type=scope_type,
|
||||
@ -111,45 +112,44 @@ class InitializationObjectStore(ObjectStore):
|
||||
if not parsed:
|
||||
raise ValueError('Invalid object representation format')
|
||||
|
||||
try:
|
||||
if owner is self._root_owner:
|
||||
self._initializing = True
|
||||
if owner is self._root_owner:
|
||||
self._initializing = True
|
||||
|
||||
class_obj = parsed['type'] or default_type
|
||||
if not class_obj:
|
||||
raise ValueError(
|
||||
'Invalid object representation: '
|
||||
'no type information was provided')
|
||||
if isinstance(class_obj, dsl_types.MuranoTypeReference):
|
||||
class_obj = class_obj.type
|
||||
object_id = parsed['id']
|
||||
obj = None if object_id is None else self._store.get(object_id)
|
||||
if not obj:
|
||||
obj = murano_object.MuranoObject(
|
||||
class_obj, helpers.weak_proxy(owner),
|
||||
name=parsed['name'],
|
||||
object_id=object_id if self._keep_ids else None)
|
||||
self.put(obj, object_id or obj.object_id)
|
||||
class_obj = parsed['type'] or default_type
|
||||
if not class_obj:
|
||||
raise ValueError(
|
||||
'Invalid object representation: '
|
||||
'no type information was provided')
|
||||
if isinstance(class_obj, dsl_types.MuranoTypeReference):
|
||||
class_obj = class_obj.type
|
||||
object_id = parsed['id']
|
||||
obj = None if object_id is None else self._store.get(object_id)
|
||||
if not obj:
|
||||
obj = murano_object.MuranoObject(
|
||||
class_obj, helpers.weak_proxy(owner),
|
||||
name=parsed['name'],
|
||||
object_id=object_id if self._keep_ids else None)
|
||||
self.put(obj, object_id or obj.object_id)
|
||||
|
||||
system_value = ObjectStore._get_designer_attributes(
|
||||
parsed['extra'])
|
||||
self._designer_attributes_store[object_id] = system_value
|
||||
system_value = ObjectStore._get_designer_attributes(
|
||||
parsed['extra'])
|
||||
self._designer_attributes_store[object_id] = system_value
|
||||
|
||||
if context is None:
|
||||
context = self.executor.create_object_context(obj)
|
||||
if context is None:
|
||||
context = self.executor.create_object_context(obj)
|
||||
|
||||
def run_initialize():
|
||||
self._initializers.extend(
|
||||
obj.initialize(context, parsed['properties']))
|
||||
def run_initialize():
|
||||
self._initializers.extend(
|
||||
obj.initialize(context, parsed['properties']))
|
||||
|
||||
run_initialize()
|
||||
if owner is self._root_owner:
|
||||
self._initializing = False
|
||||
run_initialize()
|
||||
if owner is self._root_owner:
|
||||
self._initializing = False
|
||||
run_initialize()
|
||||
finally:
|
||||
if owner is self._root_owner:
|
||||
with helpers.with_object_store(self.parent_store):
|
||||
for fn in self._initializers:
|
||||
fn()
|
||||
|
||||
if owner is self._root_owner:
|
||||
with helpers.with_object_store(self.parent_store):
|
||||
for fn in self._initializers:
|
||||
fn()
|
||||
|
||||
return obj
|
||||
|
@ -128,10 +128,11 @@ def prepare_context(exc, cls):
|
||||
context.register_function(bool_)
|
||||
context.register_function(not_null)
|
||||
context.register_function(check)
|
||||
context.register_function(class_factory(context))
|
||||
context.register_function(owned)
|
||||
context.register_function(not_owned)
|
||||
context.register_function(finalize)
|
||||
for fn in class_factory(context):
|
||||
context.register_function(fn)
|
||||
return context
|
||||
|
||||
|
||||
@ -295,7 +296,24 @@ def class_factory(context):
|
||||
'muranoType': name.type.name
|
||||
})
|
||||
|
||||
return class_
|
||||
@specs.parameter('schema', Schema)
|
||||
@specs.parameter('type_', dsl.MuranoTypeParameter(
|
||||
nullable=False, context=context))
|
||||
@specs.parameter('default_type', dsl.MuranoTypeParameter(
|
||||
nullable=True, context=context))
|
||||
@specs.parameter('version_spec', yaqltypes.String(True))
|
||||
@specs.parameter(
|
||||
'exclude_properties', yaqltypes.Sequence(nullable=True))
|
||||
@specs.method
|
||||
def template(schema, type_, exclude_properties=None,
|
||||
default_type=None, version_spec=None):
|
||||
result = class_(schema, type_, default_type, version_spec)
|
||||
result.data['owned'] = True
|
||||
if exclude_properties:
|
||||
result.data['excludedProperties'] = exclude_properties
|
||||
return result
|
||||
|
||||
return class_, template
|
||||
|
||||
|
||||
@specs.parameter('schema', Schema)
|
||||
|
@ -21,19 +21,13 @@ from murano.dsl import dsl_types
|
||||
from murano.dsl import helpers
|
||||
|
||||
|
||||
class DumpTypes(object):
|
||||
Serializable = 'Serializable'
|
||||
Inline = 'Inline'
|
||||
Mixed = 'Mixed'
|
||||
All = {Serializable, Inline, Mixed}
|
||||
|
||||
|
||||
class ObjRef(object):
|
||||
def __init__(self, obj):
|
||||
self.ref_obj = obj
|
||||
|
||||
|
||||
def serialize(obj, executor, serialization_type=DumpTypes.Serializable):
|
||||
def serialize(obj, executor,
|
||||
serialization_type=dsl_types.DumpTypes.Serializable):
|
||||
with helpers.with_object_store(executor.object_store):
|
||||
return serialize_model(
|
||||
obj, executor, True,
|
||||
@ -45,7 +39,7 @@ def serialize(obj, executor, serialization_type=DumpTypes.Serializable):
|
||||
|
||||
def _serialize_object(root_object, designer_attributes, allow_refs,
|
||||
executor, serialize_actions=True,
|
||||
serialization_type=DumpTypes.Serializable):
|
||||
serialization_type=dsl_types.DumpTypes.Serializable):
|
||||
serialized_objects = set()
|
||||
|
||||
obj = root_object
|
||||
@ -65,7 +59,7 @@ def serialize_model(root_object, executor,
|
||||
make_copy=True,
|
||||
serialize_attributes=True,
|
||||
serialize_actions=True,
|
||||
serialization_type=DumpTypes.Serializable):
|
||||
serialization_type=dsl_types.DumpTypes.Serializable):
|
||||
designer_attributes = executor.object_store.designer_attributes
|
||||
|
||||
if root_object is None:
|
||||
@ -138,11 +132,13 @@ def _pass12_serialize(value, parent, serialized_objects,
|
||||
if isinstance(value, (dsl_types.MuranoType,
|
||||
dsl_types.MuranoTypeReference)):
|
||||
return helpers.format_type_string(value), False
|
||||
if value is helpers.get_contract_passkey():
|
||||
return value, False
|
||||
if isinstance(value, dsl_types.MuranoObject):
|
||||
result = value.to_dictionary(
|
||||
serialization_type=serialization_type, allow_refs=allow_refs)
|
||||
if designer_attributes_getter is not None:
|
||||
if serialization_type == DumpTypes.Inline:
|
||||
if serialization_type == dsl_types.DumpTypes.Inline:
|
||||
system_data = result
|
||||
else:
|
||||
system_data = result['?']
|
||||
@ -161,13 +157,13 @@ def _pass12_serialize(value, parent, serialized_objects,
|
||||
|
||||
for d_key, d_value in six.iteritems(value):
|
||||
if (isinstance(d_key, dsl_types.MuranoType) and
|
||||
serialization_type == DumpTypes.Serializable):
|
||||
serialization_type == dsl_types.DumpTypes.Serializable):
|
||||
result_key = str(d_key)
|
||||
else:
|
||||
result_key = d_key
|
||||
if (result_key == 'type' and
|
||||
isinstance(d_value, dsl_types.MuranoType) and
|
||||
serialization_type == DumpTypes.Mixed):
|
||||
serialization_type == dsl_types.DumpTypes.Mixed):
|
||||
result_value = d_value, False
|
||||
else:
|
||||
result_value = _pass12_serialize(
|
||||
|
@ -17,10 +17,12 @@ from yaql.language import specs
|
||||
from yaql.language import utils
|
||||
from yaql.language import yaqltypes
|
||||
|
||||
from murano.dsl import constants
|
||||
from murano.dsl import dsl
|
||||
from murano.dsl import dsl_types
|
||||
from murano.dsl import exceptions
|
||||
from murano.dsl import helpers
|
||||
from murano.dsl import serializer
|
||||
|
||||
|
||||
class TypeScheme(object):
|
||||
@ -187,9 +189,69 @@ class TypeScheme(object):
|
||||
version_spec or helpers.get_type(root_context)):
|
||||
raise exceptions.ContractViolationException(
|
||||
'Object of type {0} is not compatible with '
|
||||
'requested type {1}'.format(obj.type.name, name))
|
||||
'requested type {1}'.format(obj.type.name, murano_class))
|
||||
return obj
|
||||
|
||||
@specs.parameter('type_', dsl.MuranoTypeParameter(
|
||||
nullable=False, context=root_context))
|
||||
@specs.parameter('default_type', dsl.MuranoTypeParameter(
|
||||
nullable=True, context=root_context))
|
||||
@specs.parameter('value', nullable=True)
|
||||
@specs.parameter('version_spec', yaqltypes.String(True))
|
||||
@specs.parameter(
|
||||
'exclude_properties', yaqltypes.Sequence(nullable=True))
|
||||
@specs.method
|
||||
def template(engine, value, type_, exclude_properties=None,
|
||||
default_type=None, version_spec=None):
|
||||
object_store = helpers.get_object_store()
|
||||
passkey = None
|
||||
if not default_type:
|
||||
default_type = type_
|
||||
murano_class = type_.type
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, dsl_types.MuranoObject):
|
||||
obj = value
|
||||
elif isinstance(value, dsl_types.MuranoObjectInterface):
|
||||
obj = value.object
|
||||
elif isinstance(value, utils.MappingType):
|
||||
passkey = utils.create_marker('<Contract Passkey>')
|
||||
if exclude_properties:
|
||||
parsed = helpers.parse_object_definition(
|
||||
value, calling_type, context)
|
||||
props = dsl.to_mutable(parsed['properties'], engine)
|
||||
for p in exclude_properties:
|
||||
helpers.patch_dict(props, p, passkey)
|
||||
parsed['properties'] = props
|
||||
value = helpers.assemble_object_definition(parsed)
|
||||
with helpers.thread_local_attribute(
|
||||
constants.TL_CONTRACT_PASSKEY, passkey):
|
||||
with helpers.thread_local_attribute(
|
||||
constants.TL_OBJECTS_DRY_RUN, True):
|
||||
obj = object_store.load(
|
||||
value, owner, context=context,
|
||||
default_type=default_type, scope_type=calling_type)
|
||||
else:
|
||||
raise exceptions.ContractViolationException(
|
||||
'Value {0} cannot be represented as class {1}'.format(
|
||||
format_scalar(value), type_))
|
||||
if not helpers.is_instance_of(
|
||||
obj, murano_class.name,
|
||||
version_spec or helpers.get_type(root_context)):
|
||||
raise exceptions.ContractViolationException(
|
||||
'Object of type {0} is not compatible with '
|
||||
'requested type {1}'.format(obj.type.name, type_))
|
||||
|
||||
with helpers.thread_local_attribute(
|
||||
constants.TL_CONTRACT_PASSKEY, passkey):
|
||||
result = serializer.serialize(
|
||||
obj.real_this, object_store.executor,
|
||||
dsl_types.DumpTypes.Mixed)
|
||||
if exclude_properties:
|
||||
for p in exclude_properties:
|
||||
helpers.patch_dict(result, p, utils.NO_VALUE)
|
||||
return result
|
||||
|
||||
context = root_context.create_child_context()
|
||||
context.register_function(int_)
|
||||
context.register_function(string)
|
||||
@ -198,6 +260,7 @@ class TypeScheme(object):
|
||||
context.register_function(not_null)
|
||||
context.register_function(error)
|
||||
context.register_function(class_)
|
||||
context.register_function(template)
|
||||
context.register_function(owned_ref)
|
||||
context.register_function(owned)
|
||||
context.register_function(not_owned_ref)
|
||||
@ -249,12 +312,33 @@ class TypeScheme(object):
|
||||
@specs.parameter('version_spec', yaqltypes.String(True))
|
||||
@specs.method
|
||||
def class_(value, type, version_spec=None):
|
||||
if helpers.is_instance_of(
|
||||
if value is None or helpers.is_instance_of(
|
||||
value, type.type.name,
|
||||
version_spec or helpers.get_names_scope(root_context)):
|
||||
return value
|
||||
raise exceptions.ContractViolationException()
|
||||
|
||||
@specs.parameter('type_', dsl.MuranoTypeParameter(
|
||||
nullable=False, context=root_context))
|
||||
@specs.parameter('default_type', dsl.MuranoTypeParameter(
|
||||
nullable=True, context=root_context))
|
||||
@specs.parameter('value', nullable=True)
|
||||
@specs.parameter('version_spec', yaqltypes.String(True))
|
||||
@specs.parameter(
|
||||
'exclude_properties', yaqltypes.Sequence(nullable=True))
|
||||
@specs.method
|
||||
def template(value, type_, exclude_properties=None,
|
||||
default_type=None, version_spec=None):
|
||||
|
||||
if value is None or isinstance(value, utils.MappingType):
|
||||
return value
|
||||
|
||||
if helpers.is_instance_of(
|
||||
value, type_.type.name,
|
||||
version_spec or helpers.get_names_scope(root_context)):
|
||||
return value
|
||||
raise exceptions.ContractViolationException()
|
||||
|
||||
context = root_context.create_child_context()
|
||||
context.register_function(int_)
|
||||
context.register_function(string)
|
||||
@ -262,6 +346,7 @@ class TypeScheme(object):
|
||||
context.register_function(check)
|
||||
context.register_function(not_null)
|
||||
context.register_function(class_)
|
||||
context.register_function(template)
|
||||
return context
|
||||
|
||||
def _map_dict(self, data, spec, context, path):
|
||||
@ -351,6 +436,8 @@ class TypeScheme(object):
|
||||
return data
|
||||
|
||||
def _map(self, data, spec, context, path):
|
||||
if is_passkey(data):
|
||||
return data
|
||||
child_context = context.create_child_context()
|
||||
if isinstance(spec, dsl_types.YaqlExpression):
|
||||
child_context[''] = data
|
||||
@ -375,6 +462,9 @@ class TypeScheme(object):
|
||||
if data is dsl.NO_VALUE:
|
||||
data = helpers.evaluate(default, context)
|
||||
|
||||
if is_passkey(data):
|
||||
return data
|
||||
|
||||
context = self.prepare_transform_context(
|
||||
context, this, owner, default, calling_type)
|
||||
return self._map(data, self._spec, context, '')
|
||||
@ -383,6 +473,9 @@ class TypeScheme(object):
|
||||
if data is dsl.NO_VALUE:
|
||||
data = helpers.evaluate(default, context)
|
||||
|
||||
if is_passkey(data):
|
||||
return True
|
||||
|
||||
context = self.prepare_validate_context(context)
|
||||
try:
|
||||
self._map(data, self._spec, context, '')
|
||||
@ -395,3 +488,8 @@ def format_scalar(value):
|
||||
if isinstance(value, six.string_types):
|
||||
return "'{0}'".format(value)
|
||||
return six.text_type(value)
|
||||
|
||||
|
||||
def is_passkey(value):
|
||||
passkey = helpers.get_contract_passkey()
|
||||
return passkey is not None and value is passkey
|
||||
|
@ -245,9 +245,9 @@ def call_func(context, op_dot, base, name, args, kwargs,
|
||||
@specs.parameter('obj', dsl.MuranoObjectParameter(decorate=False))
|
||||
@specs.parameter('serialization_type', yaqltypes.String())
|
||||
@specs.parameter('ignore_upcasts', bool)
|
||||
def dump(obj, serialization_type=serializer.DumpTypes.Serializable,
|
||||
def dump(obj, serialization_type=dsl_types.DumpTypes.Serializable,
|
||||
ignore_upcasts=True):
|
||||
if serialization_type not in serializer.DumpTypes.All:
|
||||
if serialization_type not in dsl_types.DumpTypes.All:
|
||||
raise ValueError('Invalid Serialization Type')
|
||||
executor = helpers.get_executor()
|
||||
if ignore_upcasts:
|
||||
|
@ -38,6 +38,28 @@ Methods:
|
||||
Body:
|
||||
Return: $arg
|
||||
|
||||
testTemplateContract:
|
||||
Arguments:
|
||||
arg:
|
||||
Contract: $.template(CreatedClass2)
|
||||
Body:
|
||||
Return: $arg
|
||||
|
||||
testTemplateContractExcludePropertyFromMpl:
|
||||
Body:
|
||||
- $model:
|
||||
:CreatedClass2:
|
||||
property1: qwerty
|
||||
property2: 'not integer'
|
||||
- Return: $.testTemplateContractExcludeProperty($model)
|
||||
|
||||
testTemplateContractExcludeProperty:
|
||||
Arguments:
|
||||
arg:
|
||||
Contract: $.template(CreatedClass2, excludeProperties => [property2])
|
||||
Body:
|
||||
Return: $arg
|
||||
|
||||
testClassFromIdContract:
|
||||
Arguments:
|
||||
arg:
|
||||
|
@ -42,6 +42,9 @@ Properties:
|
||||
classProperty:
|
||||
Contract: $.class(SampleClass1)
|
||||
|
||||
templateProperty:
|
||||
Contract: $.template(SampleClass1, excludeProperties => [stringProperty])
|
||||
|
||||
defaultProperty:
|
||||
Contract: $.int()
|
||||
Default: 999
|
||||
|
@ -126,6 +126,30 @@ class TestContracts(test_case.DslTestCase):
|
||||
self.assertIsInstance(result, dsl.MuranoObjectInterface)
|
||||
self.assertEqual(object_id, result.id)
|
||||
|
||||
def test_template_contract(self):
|
||||
arg = om.Object('CreatedClass2', property1='qwerty', property2=123)
|
||||
result = self._runner.testTemplateContract(arg)
|
||||
self.assertIsInstance(result, dict)
|
||||
self.assertItemsEqual(['?', 'property1', 'property2'], result.keys())
|
||||
|
||||
def test_template_contract_fail_on_type(self):
|
||||
arg = om.Object('SampleClass2', class2Property='qwerty')
|
||||
self.assertRaises(
|
||||
exceptions.ContractViolationException,
|
||||
self._runner.testTemplateContract, arg)
|
||||
|
||||
def test_template_contract_with_property_exclusion(self):
|
||||
arg = om.Object('CreatedClass2', property1='qwerty',
|
||||
property2='INVALID')
|
||||
result = self._runner.testTemplateContractExcludeProperty(arg)
|
||||
self.assertIsInstance(result, dict)
|
||||
self.assertItemsEqual(['?', 'property1'], result.keys())
|
||||
|
||||
def test_template_contract_with_property_exclusion_from_mpl(self):
|
||||
result = self._runner.testTemplateContractExcludePropertyFromMpl()
|
||||
self.assertIsInstance(result, dict)
|
||||
self.assertItemsEqual(['?', 'property1'], result.keys())
|
||||
|
||||
def test_check_contract(self):
|
||||
arg = om.Object('SampleClass2', class2Property='qwerty')
|
||||
self.assertIsNone(self._runner.testCheckContract(arg, 100))
|
||||
|
@ -75,6 +75,15 @@ class TestSchemaGeneration(test_case.DslTestCase):
|
||||
'classProperty', ['null', 'muranoObject'])
|
||||
self.assertEqual('SampleClass1', schema.get('muranoType'))
|
||||
|
||||
def test_template_property(self):
|
||||
schema = self._test_simple_property(
|
||||
'templateProperty', ['null', 'muranoObject'])
|
||||
self.assertEqual('SampleClass1', schema.get('muranoType'))
|
||||
self.assertTrue(schema.get('owned'))
|
||||
self.assertItemsEqual(
|
||||
['stringProperty'],
|
||||
schema.get('excludedProperties'))
|
||||
|
||||
def test_default_property(self):
|
||||
schema = self._test_simple_property(
|
||||
'defaultProperty', ['null', 'integer'])
|
||||
|
11
releasenotes/notes/template-contract-b71840cbc35eb478.yaml
Normal file
11
releasenotes/notes/template-contract-b71840cbc35eb478.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- New contract function ``template`` was introduced. ``template`` works
|
||||
similar to the ``class`` in regards to the data validation but does not
|
||||
instantiate objects. Instead the data is left in the object model
|
||||
in dictionary format so that it could be instantiated later with the new()
|
||||
function. In addition it allows to exclude specified properties from
|
||||
validation and resulting template so that they could be provided later.
|
||||
Objects that are assigned to the property or argument with ``template``
|
||||
contract will be automatically converted to their object model
|
||||
representation.
|
Loading…
Reference in New Issue
Block a user