diff --git a/muranoclient/common/http.py b/muranoclient/common/http.py index 3a939c09..6544d9ff 100644 --- a/muranoclient/common/http.py +++ b/muranoclient/common/http.py @@ -18,13 +18,13 @@ import logging import os import socket +from oslo.serialization import jsonutils +from oslo.utils import encodeutils import requests import six from six.moves.urllib import parse from muranoclient.common import exceptions as exc -from muranoclient.openstack.common import jsonutils -from muranoclient.openstack.common import strutils LOG = logging.getLogger(__name__) USER_AGENT = 'python-muranoclient' @@ -83,8 +83,8 @@ class HTTPClient(object): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): - header = '-H \'%s: %s\'' % (strutils.safe_decode(key), - strutils.safe_decode(value)) + header = '-H \'%s: %s\'' % (encodeutils.safe_decode(key), + encodeutils.safe_decode(value)) curl.append(header) conn_params_fmt = [ @@ -116,7 +116,7 @@ class HTTPClient(object): content = resp.content if isinstance(content, six.binary_type): try: - content = strutils.safe_decode(resp.content) + content = encodeutils.safe_decode(resp.content) except UnicodeDecodeError: pass else: diff --git a/muranoclient/common/utils.py b/muranoclient/common/utils.py index 2795be70..b1170507 100644 --- a/muranoclient/common/utils.py +++ b/muranoclient/common/utils.py @@ -22,6 +22,9 @@ import textwrap import types import uuid +from oslo.serialization import jsonutils +from oslo.utils import encodeutils +from oslo.utils import importutils import prettytable import six import yaml @@ -29,9 +32,6 @@ import yaql import yaql.exceptions from muranoclient.common import exceptions -from muranoclient.openstack.common import importutils -from muranoclient.openstack.common import jsonutils -from muranoclient.openstack.common import strutils # Decorator for cli-args @@ -69,7 +69,7 @@ def print_list(objs, fields, field_labels, formatters={}, sortby=0): data = getattr(o, field, None) or '' row.append(data) pt.add_row(row) - print(strutils.safe_encode(pt.get_string())) + print(encodeutils.safe_encode(pt.get_string())) def print_dict(d, formatters={}): @@ -81,7 +81,7 @@ def print_dict(d, formatters={}): pt.add_row([field, formatters[field](d[field])]) else: pt.add_row([field, d[field]]) - print(strutils.safe_encode(pt.get_string(sortby='Property'))) + print(encodeutils.safe_encode(pt.get_string(sortby='Property'))) def find_resource(manager, name_or_id): @@ -135,7 +135,7 @@ def import_versioned_module(version, submodule=None): def exit(msg=''): if msg: - print(strutils.safe_encode(msg), file=sys.stderr) + print(encodeutils.safe_encode(msg), file=sys.stderr) sys.exit(1) @@ -160,7 +160,7 @@ def exception_to_str(exc): except UnicodeError: error = ("Caught '%(exception)s' exception." % {"exception": exc.__class__.__name__}) - return strutils.safe_encode(error, errors='ignore') + return encodeutils.safe_encode(error, errors='ignore') class YaqlExpression(object): diff --git a/muranoclient/openstack/common/apiclient/base.py b/muranoclient/openstack/common/apiclient/base.py index fe2b22a9..e0c7071d 100644 --- a/muranoclient/openstack/common/apiclient/base.py +++ b/muranoclient/openstack/common/apiclient/base.py @@ -26,13 +26,13 @@ Base utilities to build API operation managers and objects on top of. import abc import copy +from oslo.utils import strutils +from oslo.utils import uuidutils import six from six.moves.urllib import parse from muranoclient.openstack.common.apiclient import exceptions from muranoclient.openstack.common.gettextutils import _ -from muranoclient.openstack.common import strutils -from muranoclient.openstack.common import uuidutils def getid(obj): diff --git a/muranoclient/openstack/common/apiclient/client.py b/muranoclient/openstack/common/apiclient/client.py index 49123b34..5968d71a 100644 --- a/muranoclient/openstack/common/apiclient/client.py +++ b/muranoclient/openstack/common/apiclient/client.py @@ -35,9 +35,10 @@ except ImportError: import requests +from oslo.utils import importutils + from muranoclient.openstack.common.apiclient import exceptions from muranoclient.openstack.common.gettextutils import _ -from muranoclient.openstack.common import importutils _logger = logging.getLogger(__name__) diff --git a/muranoclient/openstack/common/cliutils.py b/muranoclient/openstack/common/cliutils.py index 9cedcc5a..34a85820 100644 --- a/muranoclient/openstack/common/cliutils.py +++ b/muranoclient/openstack/common/cliutils.py @@ -28,10 +28,11 @@ import prettytable import six from six import moves +from oslo.utils import encodeutils +from oslo.utils import strutils +from oslo.utils import uuidutils from muranoclient.openstack.common.apiclient import exceptions from muranoclient.openstack.common.gettextutils import _ -from muranoclient.openstack.common import strutils -from muranoclient.openstack.common import uuidutils def validate_args(fn, *args, **kwargs): @@ -165,7 +166,7 @@ def print_list(objs, fields, formatters=None, sortby_index=0, row.append(data) pt.add_row(row) - print(strutils.safe_encode(pt.get_string(**kwargs))) + print(encodeutils.safe_encode(pt.get_string(**kwargs))) def print_dict(dct, dict_property="Property", wrap=0): @@ -193,7 +194,7 @@ def print_dict(dct, dict_property="Property", wrap=0): col1 = '' else: pt.add_row([k, v]) - print(strutils.safe_encode(pt.get_string())) + print(encodeutils.safe_encode(pt.get_string())) def get_password(max_password_prompts=3): @@ -238,9 +239,9 @@ def find_resource(manager, name_or_id, **find_args): # now try to get entity as uuid try: if six.PY2: - tmp_id = strutils.safe_encode(name_or_id) + tmp_id = encodeutils.safe_encode(name_or_id) else: - tmp_id = strutils.safe_decode(name_or_id) + tmp_id = encodeutils.safe_decode(name_or_id) if uuidutils.is_uuid_like(tmp_id): return manager.get(tmp_id) diff --git a/muranoclient/openstack/common/importutils.py b/muranoclient/openstack/common/importutils.py deleted file mode 100644 index 20b2feaa..00000000 --- a/muranoclient/openstack/common/importutils.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# 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 related utilities and helper functions. -""" - -import sys -import traceback - - -def import_class(import_str): - """Returns a class from a string including module and class.""" - mod_str, _sep, class_str = import_str.rpartition('.') - __import__(mod_str) - try: - return getattr(sys.modules[mod_str], class_str) - except AttributeError: - raise ImportError('Class %s cannot be found (%s)' % - (class_str, - traceback.format_exception(*sys.exc_info()))) - - -def import_object(import_str, *args, **kwargs): - """Import a class and return an instance of it.""" - return import_class(import_str)(*args, **kwargs) - - -def import_object_ns(name_space, import_str, *args, **kwargs): - """Tries to import object from default namespace. - - Imports a class and return an instance of it, first by trying - to find the class in a default namespace, then failing back to - a full path if not found in the default namespace. - """ - import_value = "%s.%s" % (name_space, import_str) - try: - return import_class(import_value)(*args, **kwargs) - except ImportError: - return import_class(import_str)(*args, **kwargs) - - -def import_module(import_str): - """Import a module.""" - __import__(import_str) - return sys.modules[import_str] - - -def import_versioned_module(version, submodule=None): - module = 'muranoclient.v%s' % version - if submodule: - module = '.'.join((module, submodule)) - return import_module(module) - - -def try_import(import_str, default=None): - """Try to import a module and if it fails return default.""" - try: - return import_module(import_str) - except ImportError: - return default diff --git a/muranoclient/openstack/common/jsonutils.py b/muranoclient/openstack/common/jsonutils.py deleted file mode 100644 index 32ebc806..00000000 --- a/muranoclient/openstack/common/jsonutils.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# 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. - -''' -JSON related utilities. - -This module provides a few things: - - 1) A handy function for getting an object down to something that can be - JSON serialized. See to_primitive(). - - 2) Wrappers around loads() and dumps(). The dumps() wrapper will - automatically use to_primitive() for you if needed. - - 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson - is available. -''' - - -import codecs -import datetime -import functools -import inspect -import itertools -import sys - -if sys.version_info < (2, 7): - # On Python <= 2.6, json module is not C boosted, so try to use - # simplejson module if available - try: - import simplejson as json - except ImportError: - import json -else: - import json - -import six -import six.moves.xmlrpc_client as xmlrpclib - -from muranoclient.openstack.common import gettextutils -from muranoclient.openstack.common import importutils -from muranoclient.openstack.common import strutils -from muranoclient.openstack.common import timeutils - -netaddr = importutils.try_import("netaddr") - -_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod, - inspect.isfunction, inspect.isgeneratorfunction, - inspect.isgenerator, inspect.istraceback, inspect.isframe, - inspect.iscode, inspect.isbuiltin, inspect.isroutine, - inspect.isabstract] - -_simple_types = (six.string_types + six.integer_types - + (type(None), bool, float)) - - -def to_primitive(value, convert_instances=False, convert_datetime=True, - level=0, max_depth=3): - """Convert a complex object into primitives. - - Handy for JSON serialization. We can optionally handle instances, - but since this is a recursive function, we could have cyclical - data structures. - - To handle cyclical data structures we could track the actual objects - visited in a set, but not all objects are hashable. Instead we just - track the depth of the object inspections and don't go too deep. - - Therefore, convert_instances=True is lossy ... be aware. - - """ - # handle obvious types first - order of basic types determined by running - # full tests on nova project, resulting in the following counts: - # 572754 - # 460353 - # 379632 - # 274610 - # 199918 - # 114200 - # 51817 - # 26164 - # 6491 - # 283 - # 19 - if isinstance(value, _simple_types): - return value - - if isinstance(value, datetime.datetime): - if convert_datetime: - return timeutils.strtime(value) - else: - return value - - # value of itertools.count doesn't get caught by nasty_type_tests - # and results in infinite loop when list(value) is called. - if type(value) == itertools.count: - return six.text_type(value) - - # FIXME(vish): Workaround for LP bug 852095. Without this workaround, - # tests that raise an exception in a mocked method that - # has a @wrap_exception with a notifier will fail. If - # we up the dependency to 0.5.4 (when it is released) we - # can remove this workaround. - if getattr(value, '__module__', None) == 'mox': - return 'mock' - - if level > max_depth: - return '?' - - # The try block may not be necessary after the class check above, - # but just in case ... - try: - recursive = functools.partial(to_primitive, - convert_instances=convert_instances, - convert_datetime=convert_datetime, - level=level, - max_depth=max_depth) - if isinstance(value, dict): - return dict((k, recursive(v)) for k, v in six.iteritems(value)) - elif isinstance(value, (list, tuple)): - return [recursive(lv) for lv in value] - - # It's not clear why xmlrpclib created their own DateTime type, but - # for our purposes, make it a datetime type which is explicitly - # handled - if isinstance(value, xmlrpclib.DateTime): - value = datetime.datetime(*tuple(value.timetuple())[:6]) - - if convert_datetime and isinstance(value, datetime.datetime): - return timeutils.strtime(value) - elif isinstance(value, gettextutils.Message): - return value.data - elif hasattr(value, 'iteritems'): - return recursive(dict(value.iteritems()), level=level + 1) - elif hasattr(value, '__iter__'): - return recursive(list(value)) - elif convert_instances and hasattr(value, '__dict__'): - # Likely an instance of something. Watch for cycles. - # Ignore class member vars. - return recursive(value.__dict__, level=level + 1) - elif netaddr and isinstance(value, netaddr.IPAddress): - return six.text_type(value) - else: - if any(test(value) for test in _nasty_type_tests): - return six.text_type(value) - return value - except TypeError: - # Class objects are tricky since they may define something like - # __iter__ defined but it isn't callable as list(). - return six.text_type(value) - - -def dumps(value, default=to_primitive, **kwargs): - return json.dumps(value, default=default, **kwargs) - - -def loads(s, encoding='utf-8', **kwargs): - return json.loads(strutils.safe_decode(s, encoding), **kwargs) - - -def load(fp, encoding='utf-8', **kwargs): - return json.load(codecs.getreader(encoding)(fp), **kwargs) - - -try: - import anyjson -except ImportError: - pass -else: - anyjson._modules.append((__name__, 'dumps', TypeError, - 'loads', ValueError, 'load')) - anyjson.force_implementation(__name__) diff --git a/muranoclient/openstack/common/strutils.py b/muranoclient/openstack/common/strutils.py deleted file mode 100644 index e27948bb..00000000 --- a/muranoclient/openstack/common/strutils.py +++ /dev/null @@ -1,239 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# 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. - -""" -System-level utilities and helper functions. -""" - -import math -import re -import sys -import unicodedata - -import six - -from muranoclient.openstack.common.gettextutils import _ - - -UNIT_PREFIX_EXPONENT = { - 'k': 1, - 'K': 1, - 'Ki': 1, - 'M': 2, - 'Mi': 2, - 'G': 3, - 'Gi': 3, - 'T': 4, - 'Ti': 4, -} -UNIT_SYSTEM_INFO = { - 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')), - 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')), -} - -TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') -FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') - -SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") -SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") - - -def int_from_bool_as_string(subject): - """Interpret a string as a boolean and return either 1 or 0. - - Any string value in: - - ('True', 'true', 'On', 'on', '1') - - is interpreted as a boolean True. - - Useful for JSON-decoded stuff and config file parsing - """ - return bool_from_string(subject) and 1 or 0 - - -def bool_from_string(subject, strict=False, default=False): - """Interpret a string as a boolean. - - A case-insensitive match is performed such that strings matching 't', - 'true', 'on', 'y', 'yes', or '1' are considered True and, when - `strict=False`, anything else returns the value specified by 'default'. - - Useful for JSON-decoded stuff and config file parsing. - - If `strict=True`, unrecognized values, including None, will raise a - ValueError which is useful when parsing values passed in from an API call. - Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. - """ - if not isinstance(subject, six.string_types): - subject = six.text_type(subject) - - lowered = subject.strip().lower() - - if lowered in TRUE_STRINGS: - return True - elif lowered in FALSE_STRINGS: - return False - elif strict: - acceptable = ', '.join( - "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) - msg = _("Unrecognized value '%(val)s', acceptable values are:" - " %(acceptable)s") % {'val': subject, - 'acceptable': acceptable} - raise ValueError(msg) - else: - return default - - -def safe_decode(text, incoming=None, errors='strict'): - """Decodes incoming text/bytes string using `incoming` if they're not - already unicode. - - :param incoming: Text's current encoding - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: text or a unicode `incoming` encoded - representation of it. - :raises TypeError: If text is not an instance of str - """ - if not isinstance(text, (six.string_types, six.binary_type)): - raise TypeError("%s can't be decoded" % type(text)) - - if isinstance(text, six.text_type): - return text - - if not incoming: - incoming = (sys.stdin.encoding or - sys.getdefaultencoding()) - - try: - return text.decode(incoming, errors) - except UnicodeDecodeError: - # Note(flaper87) If we get here, it means that - # sys.stdin.encoding / sys.getdefaultencoding - # didn't return a suitable encoding to decode - # text. This happens mostly when global LANG - # var is not set correctly and there's no - # default encoding. In this case, most likely - # python will use ASCII or ANSI encoders as - # default encodings but they won't be capable - # of decoding non-ASCII characters. - # - # Also, UTF-8 is being used since it's an ASCII - # extension. - return text.decode('utf-8', errors) - - -def safe_encode(text, incoming=None, - encoding='utf-8', errors='strict'): - """Encodes incoming text/bytes string using `encoding`. - - If incoming is not specified, text is expected to be encoded with - current python's default encoding. (`sys.getdefaultencoding`) - - :param incoming: Text's current encoding - :param encoding: Expected encoding for text (Default UTF-8) - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: text or a bytestring `encoding` encoded - representation of it. - :raises TypeError: If text is not an instance of str - """ - if not isinstance(text, (six.string_types, six.binary_type)): - raise TypeError("%s can't be encoded" % type(text)) - - if not incoming: - incoming = (sys.stdin.encoding or - sys.getdefaultencoding()) - - if isinstance(text, six.text_type): - return text.encode(encoding, errors) - elif text and encoding != incoming: - # Decode text before encoding it with `encoding` - text = safe_decode(text, incoming, errors) - return text.encode(encoding, errors) - else: - return text - - -def string_to_bytes(text, unit_system='IEC', return_int=False): - """Converts a string into an float representation of bytes. - - The units supported for IEC :: - - Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it) - KB, KiB, MB, MiB, GB, GiB, TB, TiB - - The units supported for SI :: - - kb(it), Mb(it), Gb(it), Tb(it) - kB, MB, GB, TB - - Note that the SI unit system does not support capital letter 'K' - - :param text: String input for bytes size conversion. - :param unit_system: Unit system for byte size conversion. - :param return_int: If True, returns integer representation of text - in bytes. (default: decimal) - :returns: Numerical representation of text in bytes. - :raises ValueError: If text has an invalid value. - - """ - try: - base, reg_ex = UNIT_SYSTEM_INFO[unit_system] - except KeyError: - msg = _('Invalid unit system: "%s"') % unit_system - raise ValueError(msg) - match = reg_ex.match(text) - if match: - magnitude = float(match.group(1)) - unit_prefix = match.group(2) - if match.group(3) in ['b', 'bit']: - magnitude /= 8 - else: - msg = _('Invalid string format: %s') % text - raise ValueError(msg) - if not unit_prefix: - res = magnitude - else: - res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix]) - if return_int: - return int(math.ceil(res)) - return res - - -def to_slug(value, incoming=None, errors="strict"): - """Normalize string. - - Convert to lowercase, remove non-word characters, and convert spaces - to hyphens. - - Inspired by Django's `slugify` filter. - - :param value: Text to slugify - :param incoming: Text's current encoding - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: slugified unicode representation of `value` - :raises TypeError: If text is not an instance of str - """ - value = safe_decode(value, incoming, errors) - # NOTE(aababilov): no need to use safe_(encode|decode) here: - # encodings are always "ascii", error handling is always "ignore" - # and types are always known (first: unicode; second: str) - value = unicodedata.normalize("NFKD", value).encode( - "ascii", "ignore").decode("ascii") - value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() - return SLUGIFY_HYPHENATE_RE.sub("-", value) diff --git a/muranoclient/openstack/common/uuidutils.py b/muranoclient/openstack/common/uuidutils.py deleted file mode 100644 index 234b880c..00000000 --- a/muranoclient/openstack/common/uuidutils.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2012 Intel Corporation. -# All Rights Reserved. -# -# 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. - -""" -UUID related utilities and helper functions. -""" - -import uuid - - -def generate_uuid(): - return str(uuid.uuid4()) - - -def is_uuid_like(val): - """Returns validation of a value as a UUID. - - For our purposes, a UUID is a canonical form string: - aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa - - """ - try: - return str(uuid.UUID(val)) == val - except (TypeError, ValueError, AttributeError): - return False diff --git a/muranoclient/shell.py b/muranoclient/shell.py index fdcecf68..f7b56354 100644 --- a/muranoclient/shell.py +++ b/muranoclient/shell.py @@ -23,12 +23,12 @@ import logging import sys from keystoneclient.v2_0 import client as ksclient +from oslo.utils import encodeutils import six from muranoclient import client as apiclient from muranoclient.common import utils from muranoclient.openstack.common.apiclient import exceptions as exc -from muranoclient.openstack.common import strutils logger = logging.getLogger(__name__) @@ -394,7 +394,7 @@ def main(args=None): if '--debug' in args or '-d' in args: raise else: - print(strutils.safe_encode(six.text_type(e)), file=sys.stderr) + print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) sys.exit(1) diff --git a/muranoclient/tests/fakes.py b/muranoclient/tests/fakes.py index 883e33c9..7ce13df4 100644 --- a/muranoclient/tests/fakes.py +++ b/muranoclient/tests/fakes.py @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from muranoclient.openstack.common import jsonutils +from oslo.serialization import jsonutils class FakeHTTPResponse(): diff --git a/openstack-common.conf b/openstack-common.conf index 83ae41da..03ac1924 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -2,9 +2,6 @@ # The list of modules to copy from openstack-common module=apiclient.exceptions -module=importutils -module=strutils -module=jsonutils module=cliutils module=apiclient diff --git a/requirements.txt b/requirements.txt index 1ed5cc57..595af5c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,8 @@ pyOpenSSL>=0.11 requests>=1.2.1,!=2.4.0 PyYAML>=3.1.0 +oslo.serialization>=1.0.0 # Apache-2.0 +oslo.utils>=1.1.0 # Apache-2.0 + # not listed in global requirements yaql>=0.2.2,<0.3