Version-aware YAML loading
* Refactoring of "packages" Python package * Now MuranoPL YAML classes can be parsed by different engines depending on Format string in package manifest With this change MuranoPL classes will no longer be read and parsed (including YAQL expressions parsing) upon package load but only on first class access and with ability to peak different parsers depending on format version specified in manifest. As a consequence it is now possible to use different YAQL engines for different package format versions. Also startup time was greatly improved as unneeded classes are no more parsed and logos and UI forms are not loaded. Partially implements: blueprint murano-versioning Change-Id: I23fc5da1a43b405d526438329dc04aed589dee13
This commit is contained in:
parent
068831ccd8
commit
9a9f3436e1
@ -24,7 +24,7 @@ LIST_PARAMS = ('category', 'tag', 'class', 'order_by')
|
||||
ORDER_VALUES = ('fqn', 'name', 'created')
|
||||
PKG_PARAMS_MAP = {'display_name': 'name',
|
||||
'full_name': 'fully_qualified_name',
|
||||
'raw_ui': 'ui_definition',
|
||||
'ui': 'ui_definition',
|
||||
'logo': 'logo',
|
||||
'package_type': 'type',
|
||||
'description': 'description',
|
||||
|
@ -226,8 +226,22 @@ class Controller(object):
|
||||
tempf.write(content)
|
||||
package_meta['archive'] = content
|
||||
try:
|
||||
pkg_to_upload = load_utils.load_from_file(
|
||||
tempf.name, target_dir=None, drop_dir=True)
|
||||
with load_utils.load_from_file(
|
||||
tempf.name, target_dir=None,
|
||||
drop_dir=True) as pkg_to_upload:
|
||||
# extend dictionary for update db
|
||||
for k, v in PKG_PARAMS_MAP.iteritems():
|
||||
if hasattr(pkg_to_upload, k):
|
||||
package_meta[v] = getattr(pkg_to_upload, k)
|
||||
try:
|
||||
package = db_api.package_upload(
|
||||
package_meta, req.context.tenant)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
msg = _('Package with specified full '
|
||||
'name is already registered')
|
||||
LOG.exception(msg)
|
||||
raise exc.HTTPConflict(msg)
|
||||
return package.to_dict()
|
||||
except pkg_exc.PackageLoadError as e:
|
||||
msg = _("Couldn't load package from file: {0}").format(e)
|
||||
LOG.exception(msg)
|
||||
@ -236,19 +250,6 @@ class Controller(object):
|
||||
LOG.debug("Deleting package archive temporary file")
|
||||
os.remove(tempf.name)
|
||||
|
||||
# extend dictionary for update db
|
||||
for k, v in PKG_PARAMS_MAP.iteritems():
|
||||
if hasattr(pkg_to_upload, k):
|
||||
package_meta[v] = getattr(pkg_to_upload, k)
|
||||
|
||||
try:
|
||||
package = db_api.package_upload(package_meta, req.context.tenant)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
msg = _('Package with specified full name is already registered')
|
||||
LOG.exception(msg)
|
||||
raise exc.HTTPConflict(msg)
|
||||
return package.to_dict()
|
||||
|
||||
def get_ui(self, req, package_id):
|
||||
target = {'package_id': package_id}
|
||||
policy.check("get_package", req.context, target)
|
||||
|
@ -73,7 +73,7 @@ def _do_import_package(_dir, categories, update=False):
|
||||
'tags': pkg.tags,
|
||||
'logo': pkg.logo,
|
||||
'supplier_logo': pkg.supplier_logo,
|
||||
'ui_definition': pkg.raw_ui,
|
||||
'ui_definition': pkg.ui,
|
||||
'class_definitions': pkg.classes,
|
||||
'archive': pkg.blob,
|
||||
'categories': categories or []
|
||||
|
@ -244,6 +244,14 @@ def parse_version_spec(version_spec):
|
||||
return version_spec
|
||||
|
||||
|
||||
def parse_version(version):
|
||||
if isinstance(version, semantic_version.Version):
|
||||
return version
|
||||
if not version:
|
||||
version = '0'
|
||||
return semantic_version.Version.coerce(str(version))
|
||||
|
||||
|
||||
def traverse(seed, producer=None, track_visited=True):
|
||||
if not yaqlutils.is_iterable(seed):
|
||||
seed = [seed]
|
||||
|
@ -95,10 +95,7 @@ class ContinueMacro(expressions.DslExpression):
|
||||
class ParallelMacro(CodeBlock):
|
||||
def __init__(self, Parallel, Limit=None):
|
||||
super(ParallelMacro, self).__init__(Parallel)
|
||||
if Limit:
|
||||
self._limit = yaql_expression.YaqlExpression(str(Limit))
|
||||
else:
|
||||
self._limit = len(self.code_block)
|
||||
self._limit = Limit or len(self.code_block)
|
||||
|
||||
def execute(self, context):
|
||||
if not self.code_block:
|
||||
|
@ -36,8 +36,8 @@ class MuranoPackage(dsl_types.MuranoPackage):
|
||||
super(MuranoPackage, self).__init__()
|
||||
self._package_loader = weakref.proxy(package_loader)
|
||||
self._name = name
|
||||
self._version = self._parse_version(version)
|
||||
self._runtime_version = self._parse_version(runtime_version)
|
||||
self._version = helpers.parse_version(version)
|
||||
self._runtime_version = helpers.parse_version(runtime_version)
|
||||
self._requirements = {
|
||||
name: semantic_version.Spec('==' + str(self._version.major))
|
||||
}
|
||||
@ -182,11 +182,3 @@ class MuranoPackage(dsl_types.MuranoPackage):
|
||||
except exceptions.NoClassFound:
|
||||
continue
|
||||
raise exceptions.NoClassFound(name)
|
||||
|
||||
@staticmethod
|
||||
def _parse_version(version):
|
||||
if isinstance(version, semantic_version.Version):
|
||||
return version
|
||||
if not version:
|
||||
version = '0'
|
||||
return semantic_version.Version.coerce(str(version))
|
||||
|
@ -25,10 +25,12 @@ from murano.dsl import yaql_integration
|
||||
|
||||
|
||||
class YaqlExpression(dsl_types.YaqlExpression):
|
||||
def __init__(self, expression):
|
||||
def __init__(self, expression, version):
|
||||
self._version = version
|
||||
if isinstance(expression, types.StringTypes):
|
||||
self._expression = encodeutils.safe_encode(expression)
|
||||
self._parsed_expression = yaql_integration.parse(self._expression)
|
||||
self._parsed_expression = yaql_integration.parse(
|
||||
self._expression, version)
|
||||
self._file_position = None
|
||||
elif isinstance(expression, YaqlExpression):
|
||||
self._expression = expression._expression
|
||||
@ -45,6 +47,10 @@ class YaqlExpression(dsl_types.YaqlExpression):
|
||||
def expression(self):
|
||||
return self._expression
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def source_file_position(self):
|
||||
return self._file_position
|
||||
@ -60,13 +66,13 @@ class YaqlExpression(dsl_types.YaqlExpression):
|
||||
return self._expression
|
||||
|
||||
@staticmethod
|
||||
def match(expr):
|
||||
if not isinstance(expr, types.StringTypes):
|
||||
def is_expression(expression, version):
|
||||
if not isinstance(expression, types.StringTypes):
|
||||
return False
|
||||
if re.match('^[\s\w\d.:]*$', expr):
|
||||
if re.match('^[\s\w\d.:]*$', expression):
|
||||
return False
|
||||
try:
|
||||
yaql_integration.parse(expr)
|
||||
yaql_integration.parse(expression, version)
|
||||
return True
|
||||
except yaql_exceptions.YaqlParsingException:
|
||||
return False
|
||||
|
@ -80,8 +80,12 @@ def create_context():
|
||||
return ROOT_CONTEXT.create_child_context()
|
||||
|
||||
|
||||
def parse(expression):
|
||||
return ENGINE(expression)
|
||||
def choose_yaql_engine(version):
|
||||
return ENGINE
|
||||
|
||||
|
||||
def parse(expression, version):
|
||||
return choose_yaql_engine(version)(expression)
|
||||
|
||||
|
||||
def call_func(__context, __name, *args, **kwargs):
|
||||
|
@ -125,7 +125,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
|
||||
self._root_loader, app_package)
|
||||
for name in app_package.classes:
|
||||
dsl_package.register_class(
|
||||
(lambda cls: lambda: app_package.get_class(cls))(name),
|
||||
(lambda cls: lambda: get_class(app_package, cls))(name),
|
||||
name)
|
||||
if app_package.full_name == constants.CORE_LIBRARY:
|
||||
system_objects.register(dsl_package)
|
||||
@ -138,9 +138,7 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
|
||||
|
||||
if os.path.exists(package_directory):
|
||||
try:
|
||||
return load_utils.load_from_dir(
|
||||
package_directory, preload=True,
|
||||
loader=yaql_yaml_loader.YaqlYamlLoader)
|
||||
return load_utils.load_from_dir(package_directory)
|
||||
except pkg_exc.PackageLoadError:
|
||||
LOG.exception(_LE(
|
||||
'Unable to load package from cache. Clean-up...'))
|
||||
@ -159,13 +157,11 @@ class ApiPackageLoader(package_loader.MuranoPackageLoader):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as package_file:
|
||||
package_file.write(package_data)
|
||||
|
||||
return load_utils.load_from_file(
|
||||
package_file.name,
|
||||
target_dir=package_directory,
|
||||
drop_dir=False,
|
||||
loader=yaql_yaml_loader.YaqlYamlLoader,
|
||||
preload=False
|
||||
)
|
||||
with load_utils.load_from_file(
|
||||
package_file.name,
|
||||
target_dir=package_directory,
|
||||
drop_dir=False) as app_package:
|
||||
return app_package
|
||||
except IOError:
|
||||
msg = 'Unable to extract package data for %s' % package_id
|
||||
exc_info = sys.exc_info()
|
||||
@ -215,15 +211,13 @@ class DirectoryPackageLoader(package_loader.MuranoPackageLoader):
|
||||
def _build_index(self):
|
||||
for folder in self.search_package_folders(self._base_path):
|
||||
try:
|
||||
package = load_utils.load_from_dir(
|
||||
folder, preload=False,
|
||||
loader=yaql_yaml_loader.YaqlYamlLoader)
|
||||
package = load_utils.load_from_dir(folder)
|
||||
dsl_package = murano_package.MuranoPackage(
|
||||
self._root_loader, package)
|
||||
for class_name in package.classes:
|
||||
dsl_package.register_class(
|
||||
(lambda pkg, cls:
|
||||
lambda: pkg.get_class(cls))(package, class_name),
|
||||
lambda: get_class(pkg, cls))(package, class_name),
|
||||
class_name
|
||||
)
|
||||
if dsl_package.name == constants.CORE_LIBRARY:
|
||||
@ -328,3 +322,10 @@ class CombinedPackageLoader(package_loader.MuranoPackageLoader):
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.api_loader.cleanup()
|
||||
return False
|
||||
|
||||
|
||||
def get_class(package, name):
|
||||
version = package.runtime_version
|
||||
loader = yaql_yaml_loader.get_loader(version)
|
||||
contents, file_id = package.get_class(name)
|
||||
return loader(contents, file_id)
|
||||
|
@ -18,52 +18,59 @@ import yaml.composer
|
||||
import yaml.constructor
|
||||
|
||||
from murano.dsl import dsl_types
|
||||
from murano.dsl import helpers
|
||||
from murano.dsl import yaql_expression
|
||||
|
||||
|
||||
class MuranoPlDict(dict):
|
||||
pass
|
||||
def get_loader(version):
|
||||
version = helpers.parse_version(version)
|
||||
|
||||
class MuranoPlDict(dict):
|
||||
pass
|
||||
|
||||
class MuranoPlYamlConstructor(yaml.constructor.Constructor):
|
||||
def construct_yaml_map(self, node):
|
||||
data = MuranoPlDict()
|
||||
data.source_file_position = build_position(node)
|
||||
yield data
|
||||
value = self.construct_mapping(node)
|
||||
data.update(value)
|
||||
class YaqlExpression(yaql_expression.YaqlExpression):
|
||||
@staticmethod
|
||||
def match(expr):
|
||||
return yaql_expression.YaqlExpression.is_expression(expr, version)
|
||||
|
||||
def load(contents, file_id):
|
||||
def build_position(node):
|
||||
return dsl_types.ExpressionFilePosition(
|
||||
file_id,
|
||||
node.start_mark.line + 1,
|
||||
node.start_mark.column + 1,
|
||||
node.end_mark.line + 1,
|
||||
node.end_mark.column + 1)
|
||||
|
||||
class YaqlYamlLoader(yaml.Loader, MuranoPlYamlConstructor):
|
||||
pass
|
||||
class MuranoPlYamlConstructor(yaml.constructor.Constructor):
|
||||
def construct_yaml_map(self, node):
|
||||
data = MuranoPlDict()
|
||||
data.source_file_position = build_position(node)
|
||||
yield data
|
||||
value = self.construct_mapping(node)
|
||||
data.update(value)
|
||||
|
||||
class YaqlYamlLoader(yaml.Loader, MuranoPlYamlConstructor):
|
||||
pass
|
||||
|
||||
YaqlYamlLoader.add_constructor(u'tag:yaml.org,2002:map',
|
||||
MuranoPlYamlConstructor.construct_yaml_map)
|
||||
YaqlYamlLoader.add_constructor(
|
||||
u'tag:yaml.org,2002:map',
|
||||
MuranoPlYamlConstructor.construct_yaml_map)
|
||||
|
||||
# workaround for PyYAML bug: http://pyyaml.org/ticket/221
|
||||
resolvers = {}
|
||||
for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
||||
resolvers[k] = v[:]
|
||||
YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
||||
|
||||
# workaround for PyYAML bug: http://pyyaml.org/ticket/221
|
||||
resolvers = {}
|
||||
for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
||||
resolvers[k] = v[:]
|
||||
YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
||||
def yaql_constructor(loader, node):
|
||||
value = loader.construct_scalar(node)
|
||||
result = yaql_expression.YaqlExpression(value, version)
|
||||
result.source_file_position = build_position(node)
|
||||
return result
|
||||
|
||||
YaqlYamlLoader.add_constructor(u'!yaql', yaql_constructor)
|
||||
YaqlYamlLoader.add_implicit_resolver(u'!yaql', YaqlExpression, None)
|
||||
return yaml.load(contents, Loader=YaqlYamlLoader)
|
||||
|
||||
def build_position(node):
|
||||
return dsl_types.ExpressionFilePosition(
|
||||
node.start_mark.name,
|
||||
node.start_mark.line + 1,
|
||||
node.start_mark.column + 1,
|
||||
node.end_mark.line + 1,
|
||||
node.end_mark.column + 1)
|
||||
|
||||
|
||||
def yaql_constructor(loader, node):
|
||||
value = loader.construct_scalar(node)
|
||||
result = yaql_expression.YaqlExpression(value)
|
||||
result.source_file_position = build_position(node)
|
||||
return result
|
||||
|
||||
YaqlYamlLoader.add_constructor(u'!yaql', yaql_constructor)
|
||||
YaqlYamlLoader.add_implicit_resolver(u'!yaql', yaql_expression.YaqlExpression,
|
||||
None)
|
||||
return load
|
||||
|
@ -1,191 +0,0 @@
|
||||
# Copyright (c) 2014 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 imghdr
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
from murano.packages import exceptions
|
||||
|
||||
|
||||
class PackageTypes(object):
|
||||
Library = 'Library'
|
||||
Application = 'Application'
|
||||
ALL = [Library, Application]
|
||||
|
||||
|
||||
class ApplicationPackage(object):
|
||||
def __init__(self, source_directory, manifest, loader):
|
||||
self.yaml_loader = loader
|
||||
self._source_directory = source_directory
|
||||
self._full_name = None
|
||||
self._package_type = None
|
||||
self._display_name = None
|
||||
self._description = None
|
||||
self._author = None
|
||||
self._supplier = {}
|
||||
self._tags = None
|
||||
self._logo = None
|
||||
self._format = manifest.get('Format')
|
||||
self._logo_cache = None
|
||||
self._supplier_logo_cache = None
|
||||
self._blob_cache = None
|
||||
self._version = None
|
||||
self._runtime_version = None
|
||||
self._requirements = {}
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return self._full_name
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return tuple()
|
||||
|
||||
@property
|
||||
def runtime_version(self):
|
||||
return self._runtime_version
|
||||
|
||||
@property
|
||||
def requirements(self):
|
||||
return self._requirements
|
||||
|
||||
@property
|
||||
def package_type(self):
|
||||
return self._package_type
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return self._display_name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
return self._author
|
||||
|
||||
@property
|
||||
def supplier(self):
|
||||
return self._supplier
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return list(self._tags)
|
||||
|
||||
@property
|
||||
def logo(self):
|
||||
if not self._logo_cache:
|
||||
self._load_logo(False)
|
||||
return self._logo_cache
|
||||
|
||||
@property
|
||||
def supplier_logo(self):
|
||||
if not self._supplier_logo_cache:
|
||||
self._load_supplier_logo(False)
|
||||
return self._supplier_logo_cache
|
||||
|
||||
@property
|
||||
def blob(self):
|
||||
if not self._blob_cache:
|
||||
self._blob_cache = _pack_dir(self._source_directory)
|
||||
return self._blob_cache
|
||||
|
||||
def get_class(self, name):
|
||||
raise exceptions.PackageClassLoadError(name)
|
||||
|
||||
def get_resource(self, name):
|
||||
resources_dir = os.path.join(self._source_directory, 'Resources')
|
||||
if not os.path.exists(resources_dir):
|
||||
os.makedirs(resources_dir)
|
||||
return os.path.join(resources_dir, name)
|
||||
|
||||
def load(self):
|
||||
self._load_logo(True)
|
||||
self._load_supplier_logo(True)
|
||||
|
||||
def _load_logo(self, validate=False):
|
||||
logo_file = self._logo or 'logo.png'
|
||||
full_path = os.path.join(self._source_directory, logo_file)
|
||||
if not os.path.isfile(full_path) and logo_file == 'logo.png':
|
||||
self._logo_cache = None
|
||||
return
|
||||
try:
|
||||
if validate:
|
||||
if imghdr.what(full_path) != 'png':
|
||||
raise exceptions.PackageLoadError(
|
||||
'Logo is not in PNG format')
|
||||
with open(full_path) as stream:
|
||||
self._logo_cache = stream.read()
|
||||
except Exception as ex:
|
||||
trace = sys.exc_info()[2]
|
||||
raise exceptions.PackageLoadError(
|
||||
'Unable to load logo: ' + str(ex)), None, trace
|
||||
|
||||
def _load_supplier_logo(self, validate=False):
|
||||
if 'Logo' not in self._supplier:
|
||||
self._supplier['Logo'] = None
|
||||
logo_file = self._supplier['Logo'] or 'supplier_logo.png'
|
||||
full_path = os.path.join(self._source_directory, logo_file)
|
||||
if not os.path.isfile(full_path) and logo_file == 'supplier_logo.png':
|
||||
del self._supplier['Logo']
|
||||
return
|
||||
try:
|
||||
if validate:
|
||||
if imghdr.what(full_path) != 'png':
|
||||
raise exceptions.PackageLoadError(
|
||||
'Supplier Logo is not in PNG format')
|
||||
with open(full_path) as stream:
|
||||
self._supplier_logo_cache = stream.read()
|
||||
except Exception as ex:
|
||||
trace = sys.exc_info()[2]
|
||||
raise exceptions.PackageLoadError(
|
||||
'Unable to load supplier logo: ' + str(ex)), None, trace
|
||||
|
||||
@staticmethod
|
||||
def _check_full_name(full_name):
|
||||
error = exceptions.PackageFormatError('Invalid FullName')
|
||||
if re.match(r'^[\w\.]+$', full_name):
|
||||
if full_name.startswith('.') or full_name.endswith('.'):
|
||||
raise error
|
||||
if '..' in full_name:
|
||||
raise error
|
||||
else:
|
||||
raise error
|
||||
|
||||
|
||||
def _zipdir(path, zipf):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for f in files:
|
||||
abspath = os.path.join(root, f)
|
||||
relpath = os.path.relpath(abspath, path)
|
||||
zipf.write(abspath, relpath)
|
||||
|
||||
|
||||
def _pack_dir(source_directory):
|
||||
blob = io.BytesIO()
|
||||
zipf = zipfile.ZipFile(blob, mode='w')
|
||||
_zipdir(source_directory, zipf)
|
||||
zipf.close()
|
||||
|
||||
return blob.getvalue()
|
@ -17,84 +17,63 @@ import shutil
|
||||
import sys
|
||||
import types
|
||||
|
||||
import semantic_version
|
||||
import yaml
|
||||
|
||||
from murano.dsl import yaql_expression
|
||||
from murano.packages import application_package
|
||||
from murano.packages import exceptions
|
||||
from murano.packages import package_base
|
||||
|
||||
YAQL = yaql_expression.YaqlExpression
|
||||
RESOURCES_DIR_NAME = 'Resources/'
|
||||
HOT_FILES_DIR_NAME = 'HotFiles/'
|
||||
HOT_ENV_DIR_NAME = 'HotEnvironments/'
|
||||
|
||||
|
||||
class YAQL(object):
|
||||
def __init__(self, expr):
|
||||
self.expr = expr
|
||||
|
||||
|
||||
class Dumper(yaml.Dumper):
|
||||
pass
|
||||
|
||||
|
||||
def yaql_representer(dumper, data):
|
||||
return dumper.represent_scalar(u'!yaql', str(data))
|
||||
return dumper.represent_scalar(u'!yaql', data.expr)
|
||||
|
||||
|
||||
Dumper.add_representer(YAQL, yaql_representer)
|
||||
|
||||
|
||||
class HotPackage(application_package.ApplicationPackage):
|
||||
def __init__(self, source_directory, manifest, loader, runtime_version):
|
||||
super(HotPackage, self).__init__(source_directory, manifest, loader)
|
||||
class HotPackage(package_base.PackageBase):
|
||||
def __init__(self, source_directory, manifest,
|
||||
package_format, runtime_version):
|
||||
super(HotPackage, self).__init__(
|
||||
source_directory, manifest, package_format, runtime_version)
|
||||
|
||||
self._translated_class = None
|
||||
self._source_directory = source_directory
|
||||
self._translated_ui = None
|
||||
|
||||
self._full_name = manifest.get('FullName')
|
||||
if not self._full_name:
|
||||
raise exceptions.PackageFormatError(
|
||||
'FullName not specified')
|
||||
self._check_full_name(self._full_name)
|
||||
self._package_type = manifest.get('Type')
|
||||
if self._package_type not in application_package.PackageTypes.ALL:
|
||||
raise exceptions.PackageFormatError('Invalid Package Type')
|
||||
self._display_name = manifest.get('Name', self._full_name)
|
||||
self._description = manifest.get('Description')
|
||||
self._author = manifest.get('Author')
|
||||
self._supplier = manifest.get('Supplier') or {}
|
||||
self._logo = manifest.get('Logo')
|
||||
self._tags = manifest.get('Tags')
|
||||
self._version = semantic_version.Version.coerce(str(manifest.get(
|
||||
'Version', '0.0.0')))
|
||||
self._runtime_version = runtime_version
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self.full_name,
|
||||
|
||||
@property
|
||||
def requirements(self):
|
||||
return {}
|
||||
|
||||
@property
|
||||
def ui(self):
|
||||
if not self._translated_ui:
|
||||
self._translated_ui = self._translate_ui()
|
||||
return self._translated_ui
|
||||
|
||||
@property
|
||||
def raw_ui(self):
|
||||
ui_obj = self.ui
|
||||
result = yaml.dump(ui_obj, Dumper=Dumper, default_style='"')
|
||||
return result
|
||||
|
||||
def get_class(self, name):
|
||||
if name != self.full_name:
|
||||
raise exceptions.PackageClassLoadError(
|
||||
name, 'Class not defined in this package')
|
||||
if not self._translated_class:
|
||||
self._translate_class()
|
||||
return self._translated_class
|
||||
|
||||
def load(self):
|
||||
self.get_class(self.full_name)
|
||||
if not self._translated_ui:
|
||||
self._translated_ui = self._translate_ui()
|
||||
super(HotPackage, self).load()
|
||||
return self._translated_class, '<generated code>'
|
||||
|
||||
def _translate_class(self):
|
||||
template_file = os.path.join(self._source_directory, 'template.yaml')
|
||||
@ -128,7 +107,7 @@ class HotPackage(application_package.ApplicationPackage):
|
||||
|
||||
files = HotPackage._translate_files(self._source_directory)
|
||||
translated.update(HotPackage._generate_workflow(hot, files))
|
||||
self._translated_class = translated
|
||||
self._translated_class = yaml.dump(translated, Dumper=Dumper)
|
||||
|
||||
@staticmethod
|
||||
def _build_properties(hot, validate_hot_parameters):
|
||||
@ -546,4 +525,4 @@ class HotPackage(application_package.ApplicationPackage):
|
||||
groups, self.full_name),
|
||||
'Forms': forms
|
||||
}
|
||||
return translated
|
||||
return yaml.dump(translated, Dumper=Dumper)
|
||||
|
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
import string
|
||||
@ -22,15 +23,13 @@ import zipfile
|
||||
import semantic_version
|
||||
import yaml
|
||||
|
||||
from murano.engine import yaql_yaml_loader
|
||||
import murano.packages.application_package
|
||||
import murano.packages.exceptions as e
|
||||
import murano.packages.hot_package
|
||||
import murano.packages.mpl_package
|
||||
|
||||
|
||||
def load_from_file(archive_path, target_dir=None, drop_dir=False,
|
||||
loader=yaql_yaml_loader.YaqlYamlLoader, preload=True):
|
||||
@contextlib.contextmanager
|
||||
def load_from_file(archive_path, target_dir=None, drop_dir=False):
|
||||
if not os.path.isfile(archive_path):
|
||||
raise e.PackageLoadError('Unable to find package file')
|
||||
created = False
|
||||
@ -50,7 +49,7 @@ def load_from_file(archive_path, target_dir=None, drop_dir=False,
|
||||
"zip archive".format(archive_path))
|
||||
package = zipfile.ZipFile(archive_path)
|
||||
package.extractall(path=target_dir)
|
||||
return load_from_dir(target_dir, preload=preload, loader=loader)
|
||||
yield load_from_dir(target_dir)
|
||||
except ValueError as err:
|
||||
raise e.PackageLoadError("Couldn't load package from file: "
|
||||
"{0}".format(err))
|
||||
@ -63,8 +62,7 @@ def load_from_file(archive_path, target_dir=None, drop_dir=False,
|
||||
os.unlink(os.path.join(target_dir, f))
|
||||
|
||||
|
||||
def load_from_dir(source_directory, filename='manifest.yaml', preload=False,
|
||||
loader=yaql_yaml_loader.YaqlYamlLoader):
|
||||
def load_from_dir(source_directory, filename='manifest.yaml'):
|
||||
formats = {
|
||||
'MuranoPL': {
|
||||
('1.0.0', '1.0.0'): murano.packages.mpl_package.MuranoPlPackage,
|
||||
@ -104,9 +102,6 @@ def load_from_dir(source_directory, filename='manifest.yaml', preload=False,
|
||||
min_version = semantic_version.Version(key[0])
|
||||
max_version = semantic_version.Version(key[1])
|
||||
if min_version <= version <= max_version:
|
||||
package = value(source_directory, content, loader, version)
|
||||
if preload:
|
||||
package.load()
|
||||
return package
|
||||
return value(source_directory, content, parts[0], version)
|
||||
raise e.PackageFormatError(
|
||||
'Unsupported {0} format version {1}'.format(parts[0], version))
|
||||
|
@ -13,89 +13,37 @@
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import semantic_version
|
||||
import yaml
|
||||
|
||||
from murano.packages import application_package
|
||||
from murano.packages import exceptions
|
||||
from murano.packages import package_base
|
||||
|
||||
|
||||
class MuranoPlPackage(application_package.ApplicationPackage):
|
||||
def __init__(self, source_directory, manifest, loader, runtime_version):
|
||||
class MuranoPlPackage(package_base.PackageBase):
|
||||
def __init__(self, source_directory, manifest,
|
||||
package_format, runtime_version):
|
||||
super(MuranoPlPackage, self).__init__(
|
||||
source_directory, manifest, loader)
|
||||
|
||||
self._classes = None
|
||||
self._ui = None
|
||||
self._ui_cache = None
|
||||
self._raw_ui_cache = None
|
||||
self._classes_cache = {}
|
||||
|
||||
self._full_name = manifest.get('FullName')
|
||||
if not self._full_name:
|
||||
raise exceptions.PackageFormatError('FullName not specified')
|
||||
self._check_full_name(self._full_name)
|
||||
self._package_type = manifest.get('Type')
|
||||
if self._package_type not in application_package.PackageTypes.ALL:
|
||||
raise exceptions.PackageFormatError('Invalid Package Type')
|
||||
self._display_name = manifest.get('Name', self._full_name)
|
||||
self._description = manifest.get('Description')
|
||||
self._author = manifest.get('Author')
|
||||
self._supplier = manifest.get('Supplier') or {}
|
||||
source_directory, manifest, package_format, runtime_version)
|
||||
self._classes = manifest.get('Classes')
|
||||
self._ui = manifest.get('UI', 'ui.yaml')
|
||||
self._logo = manifest.get('Logo')
|
||||
self._tags = manifest.get('Tags')
|
||||
self._version = semantic_version.Version.coerce(str(manifest.get(
|
||||
'Version', '0.0.0')))
|
||||
self._runtime_version = runtime_version
|
||||
self._ui_file = manifest.get('UI', 'ui.yaml')
|
||||
self._requirements = manifest.get('Require') or {}
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return tuple(self._classes.keys())
|
||||
return self._classes.keys()
|
||||
|
||||
@property
|
||||
def ui(self):
|
||||
if not self._ui_cache:
|
||||
self._load_ui(True)
|
||||
return self._ui_cache
|
||||
full_path = os.path.join(self._source_directory, 'UI', self._ui_file)
|
||||
if not os.path.isfile(full_path):
|
||||
return None
|
||||
with open(full_path) as stream:
|
||||
return stream.read()
|
||||
|
||||
@property
|
||||
def raw_ui(self):
|
||||
if not self._raw_ui_cache:
|
||||
self._load_ui(False)
|
||||
return self._raw_ui_cache
|
||||
def requirements(self):
|
||||
return self._requirements
|
||||
|
||||
def get_class(self, name):
|
||||
if name not in self._classes_cache:
|
||||
self._load_class(name)
|
||||
return self._classes_cache[name]
|
||||
|
||||
# Private methods
|
||||
def _load_ui(self, load_yaml=False):
|
||||
if self._raw_ui_cache and load_yaml:
|
||||
self._ui_cache = yaml.load(self._raw_ui_cache, self.yaml_loader)
|
||||
else:
|
||||
ui_file = self._ui
|
||||
full_path = os.path.join(self._source_directory, 'UI', ui_file)
|
||||
if not os.path.isfile(full_path):
|
||||
self._raw_ui_cache = None
|
||||
self._ui_cache = None
|
||||
return
|
||||
try:
|
||||
with open(full_path) as stream:
|
||||
self._raw_ui_cache = stream.read()
|
||||
if load_yaml:
|
||||
self._ui_cache = yaml.load(self._raw_ui_cache,
|
||||
self.yaml_loader)
|
||||
except Exception as ex:
|
||||
trace = sys.exc_info()[2]
|
||||
raise exceptions.PackageUILoadError(str(ex)), None, trace
|
||||
|
||||
def _load_class(self, name):
|
||||
if name not in self._classes:
|
||||
raise exceptions.PackageClassLoadError(
|
||||
name, 'Class not defined in package ' + self.full_name)
|
||||
@ -104,18 +52,5 @@ class MuranoPlPackage(application_package.ApplicationPackage):
|
||||
if not os.path.isfile(full_path):
|
||||
raise exceptions.PackageClassLoadError(
|
||||
name, 'File with class definition not found')
|
||||
try:
|
||||
with open(full_path) as stream:
|
||||
self._classes_cache[name] = yaml.load(stream, self.yaml_loader)
|
||||
except Exception as ex:
|
||||
trace = sys.exc_info()[2]
|
||||
msg = 'Unable to load class definition due to "{0}"'.format(
|
||||
str(ex))
|
||||
raise exceptions.PackageClassLoadError(name, msg), None, trace
|
||||
|
||||
def load(self):
|
||||
self._classes_cache.clear()
|
||||
for class_name in self._classes:
|
||||
self.get_class(class_name)
|
||||
self._load_ui(True)
|
||||
super(MuranoPlPackage, self).load()
|
||||
with open(full_path) as stream:
|
||||
return stream.read(), full_path
|
||||
|
127
murano/packages/package.py
Normal file
127
murano/packages/package.py
Normal file
@ -0,0 +1,127 @@
|
||||
# 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 abc
|
||||
import io
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class PackageType(object):
|
||||
Library = 'Library'
|
||||
Application = 'Application'
|
||||
ALL = [Library, Application]
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Package(object):
|
||||
def __init__(self, source_directory, package_format, runtime_version):
|
||||
self._source_directory = source_directory
|
||||
self._format = package_format
|
||||
self._runtime_version = runtime_version
|
||||
self._blob_cache = None
|
||||
|
||||
@property
|
||||
def format(self):
|
||||
return self._format
|
||||
|
||||
@abc.abstractproperty
|
||||
def full_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def version(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def classes(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def runtime_version(self):
|
||||
return self._runtime_version
|
||||
|
||||
@abc.abstractproperty
|
||||
def requirements(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def package_type(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def display_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def description(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def author(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def supplier(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def tags(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def logo(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def supplier_logo(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def blob(self):
|
||||
if not self._blob_cache:
|
||||
self._blob_cache = _pack_dir(self._source_directory)
|
||||
return self._blob_cache
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_class(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_resource(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def ui(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _zip_dir(path, zip_file):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for f in files:
|
||||
abs_path = os.path.join(root, f)
|
||||
relative_path = os.path.relpath(abs_path, path)
|
||||
zip_file.write(abs_path, relative_path)
|
||||
|
||||
|
||||
def _pack_dir(source_directory):
|
||||
blob = io.BytesIO()
|
||||
zip_file = zipfile.ZipFile(blob, mode='w')
|
||||
_zip_dir(source_directory, zip_file)
|
||||
zip_file.close()
|
||||
|
||||
return blob.getvalue()
|
141
murano/packages/package_base.py
Normal file
141
murano/packages/package_base.py
Normal file
@ -0,0 +1,141 @@
|
||||
# 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 abc
|
||||
import imghdr
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import semantic_version
|
||||
|
||||
from murano.packages import exceptions
|
||||
from murano.packages import package
|
||||
|
||||
|
||||
class PackageBase(package.Package):
|
||||
def __init__(self, source_directory, manifest,
|
||||
package_format, runtime_version):
|
||||
super(PackageBase, self).__init__(
|
||||
source_directory, package_format, runtime_version)
|
||||
self._full_name = manifest.get('FullName')
|
||||
if not self._full_name:
|
||||
raise exceptions.PackageFormatError('FullName is not specified')
|
||||
self._check_full_name(self._full_name)
|
||||
self._version = semantic_version.Version.coerce(str(manifest.get(
|
||||
'Version', '0.0.0')))
|
||||
self._package_type = manifest.get('Type')
|
||||
if self._package_type not in package.PackageType.ALL:
|
||||
raise exceptions.PackageFormatError(
|
||||
'Invalid package Type {0}'.format(self._package_type))
|
||||
self._display_name = manifest.get('Name', self._full_name)
|
||||
self._description = manifest.get('Description')
|
||||
self._author = manifest.get('Author')
|
||||
self._supplier = manifest.get('Supplier') or {}
|
||||
self._logo = manifest.get('Logo')
|
||||
self._tags = manifest.get('Tags')
|
||||
|
||||
self._logo_cache = None
|
||||
self._supplier_logo_cache = None
|
||||
|
||||
@abc.abstractproperty
|
||||
def requirements(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def classes(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_class(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def ui(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return self._full_name
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def package_type(self):
|
||||
return self._package_type
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return self._display_name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
return self._author
|
||||
|
||||
@property
|
||||
def supplier(self):
|
||||
return self._supplier
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return list(self._tags)
|
||||
|
||||
@property
|
||||
def logo(self):
|
||||
return self._load_image(self._logo, 'logo.png', 'logo')
|
||||
|
||||
@property
|
||||
def supplier_logo(self):
|
||||
return self._load_image(
|
||||
self._supplier.get('Logo'), 'supplier_logo.png', 'supplier logo')
|
||||
|
||||
def get_resource(self, name):
|
||||
resources_dir = os.path.join(self._source_directory, 'Resources')
|
||||
if not os.path.exists(resources_dir):
|
||||
os.makedirs(resources_dir)
|
||||
return os.path.join(resources_dir, name)
|
||||
|
||||
def _load_image(self, file_name, default_name, what_image):
|
||||
full_path = os.path.join(
|
||||
self._source_directory, file_name or default_name)
|
||||
if not os.path.isfile(full_path) and not file_name:
|
||||
return
|
||||
try:
|
||||
if imghdr.what(full_path) != 'png':
|
||||
raise exceptions.PackageLoadError(
|
||||
'{0} is not in PNG format'.format(what_image))
|
||||
with open(full_path) as stream:
|
||||
return stream.read()
|
||||
except Exception as ex:
|
||||
trace = sys.exc_info()[2]
|
||||
raise exceptions.PackageLoadError(
|
||||
'Unable to load {0}: {1}'.format(what_image, ex)), None, trace
|
||||
|
||||
@staticmethod
|
||||
def _check_full_name(full_name):
|
||||
error = exceptions.PackageFormatError('Invalid FullName ' + full_name)
|
||||
if re.match(r'^[\w\.]+$', full_name):
|
||||
if full_name.startswith('.') or full_name.endswith('.'):
|
||||
raise error
|
||||
if '..' in full_name:
|
||||
raise error
|
||||
else:
|
||||
raise error
|
@ -227,8 +227,8 @@ class TestCatalogApi(test_base.ControllerTest, test_base.MuranoApiTestCase):
|
||||
'tags': pkg.tags,
|
||||
'logo': pkg.logo,
|
||||
'supplier_logo': pkg.supplier_logo,
|
||||
'ui_definition': pkg.raw_ui,
|
||||
'class_definitions': pkg.classes,
|
||||
'ui_definition': pkg.ui,
|
||||
'class_definitions': tuple(pkg.classes),
|
||||
'archive': pkg.blob,
|
||||
'categories': [],
|
||||
}
|
||||
@ -322,7 +322,10 @@ This is a fake zip archive
|
||||
--BOUNDARY--'''
|
||||
|
||||
with mock.patch('murano.packages.load_utils.load_from_file') as lff:
|
||||
lff.return_value = package_from_dir
|
||||
ctxmgr = mock.Mock()
|
||||
ctxmgr.__enter__ = mock.Mock(return_value=package_from_dir)
|
||||
ctxmgr.__exit__ = mock.Mock(return_value=False)
|
||||
lff.return_value = ctxmgr
|
||||
|
||||
# Uploading a non-public package
|
||||
req = self._post(
|
||||
|
@ -15,8 +15,6 @@
|
||||
import fnmatch
|
||||
import os.path
|
||||
|
||||
import yaml
|
||||
|
||||
from murano.dsl import murano_package
|
||||
from murano.dsl import namespace_resolver
|
||||
from murano.dsl import package_loader
|
||||
@ -25,22 +23,26 @@ from murano.tests.unit.dsl.foundation import object_model
|
||||
|
||||
|
||||
class TestPackage(murano_package.MuranoPackage):
|
||||
def __init__(self, package_loader, name, version,
|
||||
def __init__(self, pkg_loader, name, version,
|
||||
runtime_version, requirements, configs):
|
||||
self.__configs = configs
|
||||
super(TestPackage, self).__init__(
|
||||
package_loader, name, version,
|
||||
pkg_loader, name, version,
|
||||
runtime_version, requirements)
|
||||
|
||||
def get_class_config(self, name):
|
||||
return self.__configs.get(name, {})
|
||||
|
||||
def get_resource(self, name):
|
||||
pass
|
||||
|
||||
|
||||
class TestPackageLoader(package_loader.MuranoPackageLoader):
|
||||
_classes_cache = {}
|
||||
|
||||
def __init__(self, directory, package_name, parent_loader=None):
|
||||
self._package_name = package_name
|
||||
self._yaml_loader = yaql_yaml_loader.get_loader('1.0')
|
||||
if directory in TestPackageLoader._classes_cache:
|
||||
self._classes = TestPackageLoader._classes_cache[directory]
|
||||
else:
|
||||
@ -80,7 +82,7 @@ class TestPackageLoader(package_loader.MuranoPackageLoader):
|
||||
|
||||
def _load_class(self, class_def_file):
|
||||
with open(class_def_file) as stream:
|
||||
data = yaml.load(stream, yaql_yaml_loader.YaqlYamlLoader)
|
||||
data = self._yaml_loader(stream.read(), class_def_file)
|
||||
|
||||
if 'Name' not in data:
|
||||
return
|
||||
|
@ -17,6 +17,7 @@
|
||||
import re
|
||||
|
||||
import mock
|
||||
import semantic_version
|
||||
import yaql
|
||||
from yaql.language import exceptions
|
||||
from yaql.language import utils
|
||||
@ -30,7 +31,6 @@ ROOT_CLASS = 'io.murano.Object'
|
||||
|
||||
|
||||
class TestNamespaceResolving(base.MuranoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNamespaceResolving, self).setUp()
|
||||
|
||||
@ -114,7 +114,6 @@ class Bunch(object):
|
||||
|
||||
|
||||
class TestHelperFunctions(base.MuranoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHelperFunctions, self).setUp()
|
||||
|
||||
@ -132,7 +131,6 @@ class TestHelperFunctions(base.MuranoTestCase):
|
||||
'atom': ('some', (1, 'atom'), 'hi!'),
|
||||
'sample': ('atom', (0, 1, 2, 3, 4))
|
||||
})
|
||||
print yaql_value(1)
|
||||
context = yaql.create_context()
|
||||
evaluated_value = helpers.evaluate(yaql_value, context)
|
||||
evaluated_complex_value = helpers.evaluate(complex_value, context)
|
||||
@ -142,50 +140,52 @@ class TestHelperFunctions(base.MuranoTestCase):
|
||||
|
||||
|
||||
class TestYaqlExpression(base.MuranoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._version = semantic_version.Version.coerce('1.0')
|
||||
super(TestYaqlExpression, self).setUp()
|
||||
|
||||
def test_expression(self):
|
||||
yaql_expr = yaql_expression.YaqlExpression('string')
|
||||
yaql_expr = yaql_expression.YaqlExpression('string', self._version)
|
||||
|
||||
self.assertEqual('string', yaql_expr.expression)
|
||||
|
||||
def test_unicode_expression(self):
|
||||
yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'")
|
||||
yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'",
|
||||
self._version)
|
||||
|
||||
self.assertEqual(u"'yaql ♥ unicode'".encode('utf-8'),
|
||||
yaql_expr.expression)
|
||||
|
||||
def test_unicode_expression_expression(self):
|
||||
yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'")
|
||||
yaql_expr2 = yaql_expression.YaqlExpression(yaql_expr)
|
||||
yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'",
|
||||
self._version)
|
||||
yaql_expr2 = yaql_expression.YaqlExpression(yaql_expr, self._version)
|
||||
|
||||
self.assertEqual(u"'yaql ♥ unicode'".encode('utf-8'),
|
||||
yaql_expr2.expression)
|
||||
|
||||
def test_evaluate_calls(self):
|
||||
string = 'string'
|
||||
expected_calls = [mock.call(string),
|
||||
expected_calls = [mock.call(string, self._version),
|
||||
mock.call().evaluate(context=None)]
|
||||
|
||||
with mock.patch('murano.dsl.yaql_integration.parse') as mock_parse:
|
||||
yaql_expr = yaql_expression.YaqlExpression(string)
|
||||
yaql_expr = yaql_expression.YaqlExpression(string, self._version)
|
||||
yaql_expr(None)
|
||||
|
||||
self.assertEqual(expected_calls, mock_parse.mock_calls)
|
||||
|
||||
def test_match_returns(self):
|
||||
expr = yaql_expression.YaqlExpression('string')
|
||||
def test_is_expression_returns(self):
|
||||
expr = yaql_expression.YaqlExpression('string', self._version)
|
||||
|
||||
with mock.patch('murano.dsl.yaql_integration.parse'):
|
||||
self.assertTrue(expr.match('$some'))
|
||||
self.assertTrue(expr.match('$.someMore'))
|
||||
self.assertTrue(expr.is_expression('$some', self._version))
|
||||
self.assertTrue(expr.is_expression('$.someMore', self._version))
|
||||
|
||||
with mock.patch('murano.dsl.yaql_integration.parse') as parse_mock:
|
||||
parse_mock.side_effect = exceptions.YaqlGrammarException
|
||||
self.assertFalse(expr.match(''))
|
||||
self.assertFalse(expr.is_expression('', self._version))
|
||||
|
||||
with mock.patch('murano.dsl.yaql_integration.parse') as parse_mock:
|
||||
parse_mock.side_effect = exceptions.YaqlLexicalException
|
||||
self.assertFalse(expr.match(''))
|
||||
self.assertFalse(expr.is_expression('', self._version))
|
||||
|
Loading…
x
Reference in New Issue
Block a user