pax_global_header00006660000000000000000000000064130554565040014521gustar00rootroot0000000000000052 comment=fe128f0672b5aac1787a8f54759fe44fd6f4aeb6 yaql-1.1.3/000077500000000000000000000000001305545650400124715ustar00rootroot00000000000000yaql-1.1.3/.coveragerc000066400000000000000000000001251305545650400146100ustar00rootroot00000000000000[run] branch = True source = yaql omit = yaql/tests/* [report] ignore_errors = True yaql-1.1.3/.gitignore000066400000000000000000000010261305545650400144600ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage cover .tox nosetests.xml .testrepository # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject #IntelJ Idea .idea/ # Complexity output/*.html output/*/index.html # Sphinx doc/build # pbr generates these AUTHORS ChangeLog # Editors *~ .*.swp # Files created by releasenotes build releasenotes/build yaql-1.1.3/.gitreview000066400000000000000000000001101305545650400144670ustar00rootroot00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack/yaql.gityaql-1.1.3/.mailmap000066400000000000000000000001301305545650400141040ustar00rootroot00000000000000# Format is: # # yaql-1.1.3/.testr.conf000066400000000000000000000004771305545650400145670ustar00rootroot00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list yaql-1.1.3/CONTRIBUTING.rst000066400000000000000000000010261305545650400151310ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/yaqlyaql-1.1.3/HACKING.rst000066400000000000000000000002331305545650400142650ustar00rootroot00000000000000yaql Style Commandments =============================================== Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/yaql-1.1.3/LICENSE000066400000000000000000000236361305545650400135100ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. yaql-1.1.3/MANIFEST.in000066400000000000000000000000001305545650400142150ustar00rootroot00000000000000yaql-1.1.3/README.rst000066400000000000000000000024411305545650400141610ustar00rootroot00000000000000YAQL: Yet Another Query Language ================================ YAQL (Yet Another Query Language) is an embeddable and extensible query language, that allows performing complex queries against arbitrary objects. It has a vast and comprehensive standard library of frequently used querying functions and can be extend even further with user-specified functions. YAQL is written in python and is distributed via PyPI. Quickstart ---------- Install the latest version of yaql: .. code-block:: console pip install yaql>=1.0.0 .. Run yaql REPL: .. code-block:: console yaql .. Load a json file: .. code-block:: console yaql> @load my_file.json .. Check it loaded to current context, i.e. `$`: .. code-block:: console yaql> $ .. Run some queries: .. code-block:: console yaql> $.customers ... yaql> $.customers.orders ... yaql> $.customers.where($.age > 18) ... yaql> $.customers.groupBy($.sex) ... yaql> $.customers.where($.orders.len() >= 1 or name = "John") .. Project Resources ----------------- * `Official Documentation `_ * Project status, bugs, and blueprints are tracked on `Launchpad `_ License ------- Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 yaql-1.1.3/babel.cfg000066400000000000000000000000201305545650400142070ustar00rootroot00000000000000[python: **.py] yaql-1.1.3/doc/000077500000000000000000000000001305545650400132365ustar00rootroot00000000000000yaql-1.1.3/doc/source/000077500000000000000000000000001305545650400145365ustar00rootroot00000000000000yaql-1.1.3/doc/source/_exts/000077500000000000000000000000001305545650400156605ustar00rootroot00000000000000yaql-1.1.3/doc/source/_exts/yaqlautodoc.py000066400000000000000000000150171305545650400205630ustar00rootroot00000000000000# Copyright (c) 2016 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 importlib import operator import pkgutil import traceback import types import six from docutils import nodes from docutils.parsers import rst from docutils import utils TAG = ':yaql:' def _get_modules_names(package): """Get names of modules in package""" return sorted( map(operator.itemgetter(1), pkgutil.walk_packages(package.__path__, '{0}.'.format(package.__name__)))) def _get_functions_names(module): """Get names of the functions in the current module""" return [name for name in dir(module) if isinstance(getattr(module, name, None), types.FunctionType)] def write_method_doc(method, output): """Construct method documentation from a docstring. 1) Strip TAG 2) Embolden function name 3) Add :callAs: after :signature: """ msg = "Error: function {0} has no valid YAQL documentation." if method.__doc__: doc = method.__doc__ try: # strip TAG doc = doc[doc.index(TAG) + len(TAG):] # embolden function name line_break = doc.index('\n') yaql_name = doc[:line_break] (emit_header, is_overload) = yield yaql_name if emit_header: output.write(yaql_name) output.write('\n') output.write('~' * len(yaql_name)) output.write('\n') doc = doc[line_break:] # add :callAs: parameter try: signature_index = doc.index(':signature:') position = doc.index(' :', signature_index + len(':signature:')) if hasattr(method, '__yaql_function__'): if (method.__yaql_function__.name and 'operator' in method.__yaql_function__.name): call_as = 'operator' elif (method.__yaql_function__.is_function and method.__yaql_function__.is_method): call_as = 'function or method' elif method.__yaql_function__.is_method: call_as = 'method' else: call_as = 'function' else: call_as = 'function' call_as_str = ' :callAs: {0}\n'.format(call_as) text = doc[:position] + call_as_str + doc[position:] except ValueError: text = doc if is_overload: text = '* ' + '\n '.join(text.split('\n')) output.write(text) else: output.write(text) except ValueError: yield method.func_name output.write(msg.format(method.func_name)) def write_module_doc(module, output): """Generate and write rst document for module. Generate and write rst document for the single module. :parameter module: takes a Python module which should be documented. :type module: Python module :parameter output: takes file to which generated document will be written. :type output: file """ functions_names = _get_functions_names(module) if module.__doc__: output.write(module.__doc__) output.write('\n') seq = [] for name in functions_names: method = getattr(module, name) it = write_method_doc(method, output) try: name = six.next(it) seq.append((name, it)) except StopIteration: pass seq.sort(key=operator.itemgetter(0)) prev_name = None for i, item in enumerate(seq): name = item[0] emit_header = name != prev_name prev_name = name if emit_header: overload = i < len(seq) - 1 and seq[i + 1][0] == name else: overload = True try: item[1].send((emit_header, overload)) except StopIteration: pass output.write('\n\n') output.write('\n') def write_package_doc(package, output): """Writes rst document for the package. Generate and write rst document for the modules in the given package. :parameter package: takes a Python package which should be documented :type package: Python module :parameter output: takes file to which generated document will be written. :type output: file """ modules = _get_modules_names(package) for module_name in modules: module = importlib.import_module(module_name) write_module_doc(module, output) def generate_doc(source): try: package = importlib.import_module(source) except ImportError: return 'Error: No such module {0}'.format(source) out = six.StringIO() try: if hasattr(package, '__path__'): write_package_doc(package, out) else: write_module_doc(package, out) res = out.getvalue() return res except Exception as e: return '.. code-block:: python\n\n Error: {0}\n {1}\n\n'.format( str(e), '\n '.join([''] + traceback.format_exc().split('\n'))) class YaqlDocNode(nodes.General, nodes.Element): source = None def __init__(self, source): self.source = source super(YaqlDocNode, self).__init__() class YaqlDocDirective(rst.Directive): has_content = False required_arguments = 1 def run(self): return [YaqlDocNode(self.arguments[0])] def render(app, doctree, fromdocname): for node in doctree.traverse(YaqlDocNode): new_doc = utils.new_document('YAQL', doctree.settings) content = generate_doc(node.source) rst.Parser().parse(content, new_doc) node.replace_self(new_doc.children) def setup(app): app.info('Loading the yaql documenter extension') app.add_node(YaqlDocNode) app.add_directive('yaqldoc', YaqlDocDirective) app.connect('doctree-resolved', render) return {'version': '0.1'} yaql-1.1.3/doc/source/conf.py000077500000000000000000000046371305545650400160520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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 os import sys sys.path.insert(0, os.path.join(os.path.abspath('.'), '_exts')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', # 'oslosphinx', 'yaqlautodoc' ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'yaql' copyright = u'2013, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. # intersphinx_mapping = {'http://docs.python.org/': None} yaql-1.1.3/doc/source/contributing.rst000066400000000000000000000001121305545650400177710ustar00rootroot00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rstyaql-1.1.3/doc/source/extending_yaql.rst000066400000000000000000000553521305545650400203150ustar00rootroot00000000000000Customizing and extending yaql ============================== Configuring yaql parser ----------------------- yaql has two main points of customization: * yaql engine settings allow one to configure the query language and execution flags shared by all queries that are processed by the same YAQL parser. This includes the list of available operators, yaql resources quotas, and other engine parameters. * By customizing the yaql context object, one can change the list of available functions (add new, override existing) and change naming conventions. Engine options are supplied to the `yaql.language.factory.YaqlFactory` class. YaqlFactory is used to create instances of the YaqlEngine, that is the YAQL parser. This is done by calling the `create` method of the factory. Once the engine is created, it captures all the factory options so that they cannot be changed for that particular parser any longer. In general, it is recommended to have one yal engine instance per application, because construction of the parser is an expensive operation and the parser has no internal state and thus can be reused for several queries, including in different threads. However, the host may have several YAQL parsers for different option sets or dialects. On the contrary, the context object is cheap to create and is mutable by design, since it holds the input data for the query. In most cases it is a good idea to execute each query in its own context, although all such contexts might be the children of some other, fixed context that is created just once. Customizing operators ~~~~~~~~~~~~~~~~~~~~~ `YaqlFactory` object holds an operator table that is recognized by the parser produced by it. By default, it is prepopulated with standard operators and most applications never need to do anything here. However, if the host wants to have some custom operator symbol available in its expressions, this table needs to be modified. `YaqlFactory` holds the operator symbols and other information about the operator that is relevant to the parser, but not the implementations. The implementations (what operators actually do) are put in the `context` and can be configured for each expression, but the list of available operator symbols cannot be changed for the parser once it has been built. Each operator in the table is represented by the tuple `(op_symbols, op_type, op_alias): * op_symbols are the operator symbols. There are no limitations on how the operators can be called as long as they do not contain whitespaces. It can be one symbol (like `+`), several symbols (like `=~`) or even a word (like `not`). List/index and dictionary expressions require `[]` and `{}` binary left associative operators to be present in the table. Otherwise corresponding constructions will not work (and can be disabled by removing corresponding operators from the table) * op_type is one of the values in `yaql.language.factory.OperatorType` enumeration: BINARY_LEFT_ASSOCIATIVE and BINARY_RIGHT_ASSOCIATIVE for binary operators, PREFIX_UNARY and SUFFIX_UNARY for unary operators, NAME_VALUE_PAIR for the keyword/mapping pseudo-operator (that is `=>`, by default). * op_alias is the alias name for the operator. See YAQL language reference on how operator aliases are used. Aliases are optional and most operators do not have it and thus are represented by a tuple of two elements. Operators are grouped by their precedence. Operators with a higher precedence come first in the operator table. Operators within the same group have the same precedence. Groups are separated by an empty tuple (`()`). The operator table, which is a list of tuples, is available through the `operators` attribute of the factory and is open for modification. To simplify the editing, `YaqlFactory` provides the `insert_operator` helper method to insert an operator before of after some other existing operator to get the desired precedence. Execution options ~~~~~~~~~~~~~~~~~ Execution options are the settings and flags that affect execution of each query and are accessible and processed by both yaql runtime and standard library functions. Options are passed to the `create` method of the `YaqlFactory` class in a plain key-value dictionary. The factory does not process the dictionary but rather attaches the options to the constructed engine (YAQL parser) after which they cannot be changed. However, the engine provides a `copy` method that can be used to clone the engine with different execution options. The options that are honored by the yaql are: * `"yaql.limitIterators": ` limit iterators by the given number of elements. When set, each time any function declares its parameter to be iterator, that iterator is modified to not produce more than a given number of items. Also, upon the expression evaluation, all the output collections and iterators are limited as well. If not set (or set to -1) the result data is allowed to contain endless iterators that would cause errors if the result where to be serialized (to JSON or any other format). Default is -1 (do not limit). * `"yaql.memoryQuota": ` - the memory usage quota (in bytes) for all data produced by the expression (or any part of it). Default is -1 (do not limit). * `"yaql.convertTuplesToLists": `. When set to true, yaql converts all tuples in the expression result to lists. The default is `True`. * `"yaql.convertSetsToLists": `. When set to true, yaql converts all sets in the expression result to lists. Otherwise the produced result may contain sets that are not JSON-serializable. The default is `False`. * `"yaql.iterableDicts": `. When set to true, dictionaries are considered to be iterable and iteration over dictionaries produces their keys (as in Python and yaql 0.2). Defaults to `False`. Consumers are free to use their own settings or use the options dictionary to provide some other environment information to their own custom functions. Other engine customizations ~~~~~~~~~~~~~~~~~~~~~~~~~~~ `YaqlFactory` class initializer has two optional parameters that can be used to further customize the YAQL parser: * `keyword_operator` allows one to configure keyword/mapping symbol. The default is `=>`. Ability to pass named arguments can be disabled altogether if `None` or empty string is provided. * `allow_delegates` enables or disables delegate expression parsing. Default is False (disabled). Working with contexts ~~~~~~~~~~~~~~~~~~~~~ Context is an interface that yaql runtime uses to obtain a list of available functions and variables. Any context object must implement `yaql.language.contexts.ContextBase` interface and yaql provides several such implementations ranging from the `yaql.language.contexts.Context` class, that is a basic context implementation, to contexts that allow one to merge several other contexts into one or link an existing context into the list of contexts. Any context may have a parent context. Any lookup that is done in the context is also performed in its parent context, extending all the way up its chain of contexts. During expression evaluation, yaql can create a long chain of contexts that are all children of the context that was originally passed with the query. Most of the yaql customizations are achieved by context manipulations. This includes: * Overriding YAQL functions * Building context chains and evaluating sub-expressions in different contexts * Composing context chains from pre-built contexts * Having custom `ContextBase` implementations and mixing them with regular contexts in the single chain In fact, it is the context which provides the entry point for expression evaluation. And thus custom context implementations may completely change the way queries are evaluated. There are three ways to create a context instance: #. Directly instantiate one of `ContextBase` implementations to get an empty context #. Call `create_child_context` method on any existing context object to get a child context #. Use `yaql.create_context` function to creates the root context that is prepopulated with YAQL standard library functions `yaql.create_context` allows one to selectively disable standard library modules. Naming conventions ~~~~~~~~~~~~~~~~~~ Naming conventions define how Python functions and parameter names are translated into YAQL names. Conventions are implementations of the `yaql.language.conventions.Convention` interface that has just two methods: one to translate the function name and another to translate the function parameter name. yaql has two implementations included: * `yaql.language.conventions.CamelCaseConvention' that translates Python conventions into camel case. For example, it will convert `my_func(arg_name)` into `myFunc(argName)`. This convention is used by default. * `yaql.language.conventions.PythonConvention' that leaves function and parameter names intact. Each context, either directly or indirectly through its parent context, is configured to use some convention. When a function is registered in the context, its name and parameters are translated with the convention methods. Also, regardless of convention used, all trailing underscores are stripped from the names. This makes it possible to define several Python functions that differ only by trailing underscores and get the same name in YAQL (to create several overloads of single function). Also, this allow one to have function or parameter names that would otherwise conflict with Python keywords. Instance of convention class can be specified as a context initializer parameter or as a parameter of `yaql.create_context` function. Child contexts created with the `create_child_context` method inherit their parent convention. Extending yaql -------------- Extending yaql with new functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For a function to become available to YAQL queries, it must be present in the provided context object. The default context implementation (`yaql.language.contexts.Context`) has a `register_function` method to register the function implementation. In yaql, all functions are represented by instances of the `yaql.language.specs.FunctionDefinition` class. FunctionDefinition describes the complete function signature including: * Function name * List of parameters - instances of `yaql.language.specs.ParameterDefinition` * Function payload (Python callable) * Function type: function, method or extension method * The flag to disable the keyword arguments syntax for the function * Documentation string * Custom function metadata (dict) `register_function` method can accept either an instance of the `FunctionDefinition` class or a regular Python function. In the latter case, it constructs a `FunctionDefinition` instance from the declaration of the function using Python introspection. Because a YAQL function signature has much more information than the Python one, yaql provides a number of function decorators that can be used to fill the missing properties. The decorators are located in the `yaql.language.specs` module. Below is the list of available function decorators: * ``@name(function_name)``: set function name to be `function_name` rather than its Python name * ``@parameter(...)`` is used to declare the type of one of the function parameters * ``@inject(...)`` is used to declare a hidden function parameter * ``@method`` declares function to be YAQL method * ``@extension_method`` declares function to be YAQL extension method * ``@no_kwargs`` disables the keyword arguments syntax for the function * ``@meta(name, value)`` appends the `name` attribute with the given value to the function metadata dictionary Specifying function parameter types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When yaql constructs `FunctionDefinition`, it collects all possible information about its parameters. For each parameter, it records its name, position, whether it is a keyword-only argument (available in Python 3), whether it is an `*args` or `**kwargs`, and its default parameter value. The only parameter attribute that cannot be obtained through retrospection is the parameter type. For that purpose, yaql has a ``@parameter(name, type)`` decorator that can be used to explicitly declare the parameter type. `name` must match the name of one of the function parameters, and `type` must be of the `yaql.language.yaqltypes.SmartType` type. `SmartType` is the base class for all yaql type descriptors - classes that check if the value is compatible with the desired type and can do type conversion between compatible types. YAQL type system slightly differs from Python's: * Strings are not considered to be collections of characters * Booleans are not integers * Dictionaries are not iterable * For most of the types one can specify if the `null` (`None`) value is acceptable `yaql.language.yaqltypes` module has many useful smart-type classes. The most generic smart-type for primitive types is the `PythonType` class, that validates if the value is instance of a given Python type. Due to the mentioned differences between YAQL and Python type systems and because Python types have a lot of nuances (several string types, differences between Python 2 and Python 3, separation between mutable and immutable type versions: list-tuple, set-frozenset, dict-FrozenDict, which is missing in Python and provided by the yaql instead), yaql provides specialized smart-types for most primitive types: * `String` - str and unicode * `Integer` * `Number` - integer of float * `DateTime` * `Sequence` - fixed-size iterable collection, except for the dictionary * `Iterable` - any iterable or generator * `Iterator` - iterator over the iterable And several specialized variants that enforce particular representation in the YAQL syntax: * `Keyword` * `BooleanConstant` * `NumericConstant` * `StringConstant` It is also possible to aggregate several smart-types so that the value can be of any given type or conform to all of them: * `AnyOf` * `Chain` * `NotOfType` These three smart-types accept other smart-type(s) as their initializer parameter(s). In addition to the smart-types, the second parameter of the `@parameter` can be a Python type. For example, ``@parameter("name", unicode)`` or ``@parameter("name", unicode, nullable=True)``. In this case the Python type is automatically wrapped in the `PythonType` smart-type. If nullability is not specified, yaql tries to infer it from the parameter declaration - it is nullable only if the parameter has its default value set to `None`. Lazy evaluated function parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All the smart-types from the previous section are for parameters that are evaluated before the function gets invoked. But sometimes the function might need the parameter to remain unevaluated so that it can be evaluated by the function itself, possibly with additional parameters or in a different context. There are two possible representations of non-evaluated arguments: * Get it as a Python callable that the function can call to do the evaluation * Get it as a YAQL expression (AST), that can be analyzed The first method is available through the `Lambda` smart-type. The parameter, which is declared as a ``Lambda()``, has an `*args/**kwargs` signature and can be called from the function: ``parameter(arg1, arg2)``. If it was declared as ``Lambda(with_context=True)`` the function may invoke it in a context, other than that which is used for the function: ``parameter(new_context, arg1, arg2)``. ``Lambda(method=True)`` specifies that the parameter must be a method and the caller can specify the receiver object for it: ``parameter(receiver, arg1, arg2)``. Parameters can also be combined: ``Lambda(with_context=True, method=True)`` so the callable is invoked as ``parameter(receiver, new_context, arg1, arg2)``. All supplied callable arguments are automatically published to the `$1` (`$`), `$2` and so on context variables for the context in which the callable will be executed. The second method is available through the `YaqlExpression` smart-type. It also allows one to request the parameter to be of a particular expression type rather than an arbitrary YAQL expression. Auto-injected function parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Besides regular parameters, yaql also supports auto-injected (hidden) parameters. This is also known as a function parameter dependency injection. The values of injected parameters come from the yaql runtime rather than from the caller. Functions use injected parameters to get information on their execution environment. Auto-injected parameters are declared using the ``@inject(...)`` decorator, which has exactly the same signature as `@parameter` with the only difference being that `@inject` checks that that the supplied smart-type is an instance of the `yaql.language.yaqltypes.HiddenParameterType` class (in addition to `SmartType`), whereas the `@parameter` decorator checks that it is not. This difference exists to clearly distinguish explicitly passed parameters from those that are injected by the system. yaql has the following hidden parameter smart types: * `Context` - injects the current function context object * `Engine` - injects `YaqlEngine` object that was used to parse the expression. Engine object may be used to access execution options or to parse some other expression * `FunctionDefinition` - `FunctionDefinition` object of the function. May be used to obtain function metadata and doc-string * `Delegate` - injects a Python callable to some other YAQL function by its name. This is a convenient way to call one YAQL function from another without depending on its Python implementation signature and location. The syntax is very similar to `Lambda` smart-type * `Super` - similar to `Delegate` - injects callable to an overload of itself from the parent context. Useful when the function overload wants to call its base implementation (analogous to Python's ``super()``) * `Receiver` - injects a method receiver object if the function was called as a method and `None` otherwise. Can be used in an extension method to distinguish the case, when it was invoked as a method rather than as a function. Do not do it without a good reason! * `YaqlInterface` - injects a convenient wrapper (`YaqlInterface`) around yaql functionality, which also encapsulates many of the values above Auto-injected parameters may appear anywhere in the function signature as they do not affect caller syntax. Implementations can add additional hidden parameters without breaking existing queries. However, it is important to call YAQL function implementations through the yaql mechanisms (such as `Delegate`), rather than to call their Python implementations directly. Automatic parameters ~~~~~~~~~~~~~~~~~~~~ In some cases there is no need to declare the parameter at all. yaql uses parameter name and default value to guess the parameter type if it was not declared. If the parameter name is `context` or `__context` it will automatically be treated as if it was declared as a `Context`. `engine`/`__engine` is considered as an `Engine`, and `yaql_interface`/`__yaql_interface` is considered as a `YaqlInterface`. The host can override this logic by providing a callable to Context's `register_function` method through the `parameter_type_func` parameter. When yaql encounters an undeclared parameter, it calls this function, passing the parameter name as an argument, and expects it to return a smart-type for the parameter. If the `parameter_type_func` callable returned `None`, yaql would assume that the smart type should be `PythonType(object)`, that is anything, except for the `None` value, unless the parameter had the default value `None`. Function resolution rules ~~~~~~~~~~~~~~~~~~~~~~~~~ Function resolution rules are used to determine the correct overload of the function when more than one overload is present in the context. Each time a function with a given list of parameters is called yaql does the following: #. Walks through the chain of context objects and collects all the implementations with a given name and appropriate type (either functions and extension methods or methods and extensions methods, depending on the call syntax). #. All found overloads are organized into layers so that overloads from the same context will be put in the same layer whereas overloads from different contexts are in different layers. Overloads from contexts that are closer to the initial context have precedence over those which were obtained from the parent contexts. Also `FunctionDefinition` may have a flag that prevents all overload lookups in the parent contexts. If the search encounters an overload with such a flag, it does not go any further in the chain. #. Scan all found overloads and exclude those, that cannot be called by the given syntax. This can happen because the overload has more mandatory parameters than the arguments in the calling expression, or because it passes the argument using the keyword name and no such parameter exists. #. Validates laziness of overload parameters. If at least one function overload has a lazy evaluated parameter all other overloads must have it in the same position. Violation of this rule causes an exception to be thrown. #. All the non-lazy parameters are evaluated. The result values are validated by appropriate smart-type instances corresponding to each parameter of each overload. All the overloads that are not type-compatible with the given arguments are excluded in each layer. #. Take first non-empty layer. If no such layer exists (that is all the overloads were excluded) then throw an exception. #. If the found layer has more than one overload, then we have an ambiguity. In this case an exception is thrown since we cannot unambiguously determine the right overload. #. Otherwise, call the single overload with previously evaluated arguments. Function development hints ~~~~~~~~~~~~~~~~~~~~~~~~~~ * Avoid side effects in your functions, unless you absolutely have to. * Do not make changes to the data structures coming from the parameters or the context. Functions that modify the data should return the modified copy rather than touch the original. * If you need to make changes to the context, create a child context and make them there. It is usually possible to pass the new context to other parts of the query. * Strongly prefer immutable data structures over mutable ones. Use `tuple`s rather than `list`s, `frozenset` instead of `set`. Python does not have a built-in immutable dictionary class so yaql provides one on its own - `yaql.language.utils.FrozenDict`. * Do not call Python implementation of YAQL functions directly. yaql provides plenty of ways to do so. * Do not reuse contexts between multiple queries unless it is intentional. However all of these contexts can be children of a single prepared context. * Do not register all the custom functions for each query. It is better to prepare all the contexts with functions at the beginning and then use child contexts for each query executed. yaql-1.1.3/doc/source/getting_started.rst000066400000000000000000000326721305545650400204710ustar00rootroot00000000000000Getting started with YAQL ========================= Introduction to YAQL -------------------- YAQL (Yet Another Query Language) is an embeddable and extensible query language that allows performing complex queries against arbitrary data structures. `Embeddable` means that you can easily integrate a YAQL query processor in your code. Queries come from your DSLs (domain specific language), user input, JSON, and so on. YAQL has a vast and comprehensive standard library of functions that can be used to query data of any complexity. Also, YAQL can be extended even further with user-specified functions. YAQL is written in Python and is distributed through PyPI. YAQL was inspired by Microsoft LINQ for Objects and its first aim is to execute expressions on the data in memory. A YAQL expression has the same role as an SQL query to databases: search and operate the data. In general, any SQL query can be transformed to a YAQL expression, but YAQL can also be used for computational statements. For example, `2 + 3*4` is a valid YAQL expression. Moreover, in YAQL, the following operations are supported out of the box: * Complex data queries * Creation and transformation of lists, dicts, and arrays * String operations * Basic math operations * Conditional expression * Date and time operations (will be supported in yaql 1.1) An interesting thing in YAQL is that everything is a function and any function can be customized or overridden. This is true even for built-in functions. YAQL cannot call any function that was not explicitly registered to be accessible by YAQL. The same is true for operators. YAQL can be used in two different ways: as an independent CLI tool, and as a Python module. Installation ------------ You can install YAQL in two different ways: #. Using PyPi: .. code-block:: console pip install yaql #. Using your system package manager (for example Ubuntu): .. code-block:: console sudo apt-get install python-yaql HowTo: Use YAQL in Python ------------------------- You can operate with YAQL from Python in three easy steps: * Create a YAQL engine * Parse a YAQL expression * Execute the parsed expression .. NOTE:: The engine should be created once for a set of operators and parser rules. It can be reused for all queries. Here is an example how it can be done with the YAML file which looks like: .. code-block:: yaml customers_city: - city: New York customer_id: 1 - city: Saint Louis customer_id: 2 - city: Mountain View customer_id: 3 customers: - customer_id: 1 name: John orders: - order_id: 1 item: Guitar quantity: 1 - customer_id: 2 name: Paul orders: - order_id: 2 item: Banjo quantity: 2 - order_id: 3 item: Piano quantity: 1 - customer_id: 3 name: Diana orders: - order_id: 4 item: Drums quantity: 1 .. code-block:: python import yaql import yaml data_source = yaml.load(open('shop.yaml', 'r')) engine = yaql.factory.YaqlFactory().create() expression = engine( '$.customers.orders.selectMany($.where($.order_id = 4))') order = expression.evaluate(data=data_source) Content of the ``order`` will be the following: .. code-block:: console [{u'item': u'Drums', u'order_id': 4, u'quantity': 1}] YAQL grammar ------------ YAQL has a very simple grammar: * Three keywords as in JSON: true, false, null * Numbers, such as 12 and 34.5 * Strings: `'foo'` and `"bar"` * Access to the data: $variable, $ * Binary and unary operators: 2 + 2, -1, 1 != 2, $list[1] Data access ~~~~~~~~~~~ Although YAQL expressions may be self-sufficient, the most important value of YAQL is its ability to operate on user-passed data. Such data is placed into variables which are accessible in a YAQL expression as `$`. The `variable_name` can contain numbers, English alphabetic characters, and underscore symbols. The `variable_name` can be empty, in this case you will use `$`. Variables can be set prior to executing a YAQL expression or can be changed during the execution of some functions. According to the convention in YAQL, function parameters, including input data, are stored in variables like `$1`, `$2`, and so on. The `$` stands for `$1`. For most cases, all function parameters are passed in one piece and can be accessed using `$`, that is why this variable is the most used one in YAQL expressions. Besides, some functions are expected to get a YAQL expression as one of the parameters (for example, a predicate for collection sorting). In this case, passed expression is granted access to the data by `$`. Strings ~~~~~~~ In YAQL, strings can be enclosed in `"` and `'`. Both types are absolutely equal and support all standard escape symbols including unicode code-points. In YAQL, both types of quotes are useful when you need to include one type of quotes into the other. In addition, ` is used to create a string where only one escape symbol \` is possible. This is especially suitable for regexp expressions. If a string does not start with a digit or `__` and contains only digits, `_`, and English letters, it is called identifier and can be used without quotes at all. An identifier can be used as a name for function, parameter or property in `$obj.property` case. Functions ~~~~~~~~~ A function call has syntax of `functionName(functionParameters)`. Brackets are necessary even if there are no parameters. In YAQL, there are two types of parameters: * Positional parameters ``foo(1, 2, someValue)`` * Named parameters ``foo(paramName1 => value1, paramName2 => 123)`` Also, a function can be called using both positional and named parameters: ``foo(1, false, param => null)``. In this case, named arguments must be written after positional arguments. In ``name => value``, `name` must be a valid identifier and must match the name of parameter in function definition. Usually, arguments can be passed in both ways, but named-only parameters are supported in YAQL since Python 3 supports them. Parameters can have default values. Named parameters is a good way to pass only needed parameters and skip arguments which can be use default values, also you can simply skip parameters in function call: ``foo(1,,3)``. In YAQL, there are three types of functions: * Regular functions: ``max(1,2)`` * Method-like functions, which are called by specifying an object for which the function is called, followed by a dot and a function call: ``stringValue.toUpper()`` * Extension methods, which can be called both ways: ``len(string)``, ``string.len()`` YAQL standard library contains hundreds of functions which belong to one of these types. Moreover, applications can add new functions and override functions from the standard library. Operators ~~~~~~~~~ YAQL supports the following types of operators out of the box: * Arithmetic: `+`. `-`, `*`, `/`, `mod` * Logical: `=`, `!=`, `>=`, `<=`, `and`, `or`, `not` * Regexp operations: `=~`, `!~` * Method call, call to the attribute: `.`, `?.` * Context pass: `->` * Indexing: `[ ]` * Membership test operations: `in` Data structures ~~~~~~~~~~~~~~~ YAQL supports these types out of the box: * Scalars YAQL supports such types as string, int. boolean. Datetime and timespan will be available after yaql 1.1 release. * Lists List creation: ``[1, 2, value, true]`` Alternative syntax: ``list(1, 2, value, true)`` List elemenets can be accesessed by index: ``$list[0]`` * Dictionaries Dict creation: ``{key1 => value1, true => 1, 0 => false}`` Alternative syntax: ``dict(key1 => value1, true => 1, 0 => false)`` Dictionaries can be indexed by keys: ``$dict[key]``. Exception will be raised if the key is missing in the dictionary. Also, you can specify value which will be returned if the key is not in the dictionary: ``dict.get(key, default)``. .. NOTE:: During iteration through the dictionary, `key` can be called like: ``$.key`` * (Optional) Sets Set creation: ``set(1, 2, value, true)`` .. NOTE:: YAQL is designed to keep input data unchanged. All the functions that look as if they change data, actually return an updated copy and keep the original data unchanged. This is one reason why YAQL is thread-safe. Basic YAQL query operations --------------------------- It is obvious that we can compare YAQL with SQL as they both are designed to solve similar tasks. Here we will take a look at the YAQL functions which have a direct equivalent with SQL. We will use YAML from `HowTo: use YAQL in Python`_ as a data source in our examples. Filtering ~~~~~~~~~ .. NOTE:: Analog is SQL WHERE The most common query to the data sets is filtering. This is a type of query which will return only elements for which the filtering query is true. In YAQL, we use ``where`` to apply filtering queries. .. code-block:: console yaql> $.customers.where($.name = John) .. code-block:: yaml - customer_id: 1 name: John orders: - order_id: 1 item: Guitar quantity: 1 Ordering ~~~~~~~~ .. NOTE:: Analog is SQL ORDER BY It may be required to sort the data returned by some YAQL query. The ``orderBy`` clause will cause the elements in the returned sequence to be sorted according to the default comparer for the type being sorted. For example, the following query can be extended to sort the results based on the profession property. .. code-block:: console yaql> $.customers.orderBy($.name) .. code-block:: yaml - customer_id: 3 name: Diana orders: - order_id: 4 item: Drums quantity: 1 - customer_id: 1 name: John orders: - order_id: 1 item: Guitar quantity: 1 - customer_id: 2 name: Paul orders: - order_id: 2 item: Banjo quantity: 2 - order_id: 3 item: Piano quantity: 1 Grouping ~~~~~~~~ .. NOTE:: Analog is SQL GROUP BY The ``groupBy`` clause allows you to group the results according to the key you specified. Thus, it is possible to group example json by gender. .. code-block:: console yaql> $.customers.groupBy($.name) .. code-block:: yaml - Diana: - customer_id: 3 name: Diana orders: - order_id: 4 item: Drums quantity: 1 - Paul: - customer_id: 2 name: Paul orders: - order_id: 2 item: Banjo quantity: 2 - order_id: 3 item: Piano quantity: 1 - John: - customer_id: 1 name: John orders: - order_id: 1 item: Guitar quantity: 1 So, here you can see the difference between ``groupBy`` and ``orderBy``. We use the same parameter `name` for both operations, but in the output for ``groupBy`` `name` is located in additional place before everything else. Selecting ~~~~~~~~~ .. NOTE:: Analog is SQL SELECT The ``select`` method allows building new objects out of objects of some collection. In the following example, the result will contain a list of name/orders pairs. .. code-block:: console yaql> $.customers.select([$.name, $.orders]) .. code-block:: console - John: - order_id: 1 item: Guitar quantity: 1 - Paul: - order_id: 2 item: Banjo quantity: 2 - order_id: 3 item: Piano quantity: 1 - Diana: - order_id: 4 item: Drums quantity: 1 Joining ~~~~~~~ .. NOTE:: Analog is SQL JOIN The ``join`` method creates a new collection by joining two other collections by some condition. .. code-block:: console yaql> $.customers.join($.customers_city, $1.customer_id = $2.customer_id, {customer=>$1.name, city=>$2.city, orders=>$1.orders}) .. code-block:: yaml - customer: John city: New York orders: - order_id: 1 item: Guitar quantity: 1 - customer: Paul city: Saint Louis orders: - order_id: 2 item: Banjo quantity: 2 - order_id: 3 item: Piano quantity: 1 - customer: Diana city: Mountain View orders: - order_id: 4 item: Drums quantity: 1 Take an element from collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ YAQL supports two general methods that can help you to take elements from collection ``skip`` and ``take``. .. code-block:: console yaql> $.customers.skip(1).take(2) .. code-block:: yaml - customer_id: 2 name: Paul orders: - order_id: 2 item: Banjo quantity: 2 - order_id: 3 item: Piano quantity: 1 - customer_id: 3 name: Diana orders: - order_id: 4 item: Drums quantity: 1 First element of collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``first`` method will return the first element of a collection. .. code-block:: console yaql> $.customers.first() .. code-block:: yaml - customer_id: 1 name: John orders: - order_id: 1 item: Guitar quantity: 1 yaql-1.1.3/doc/source/index.rst000066400000000000000000000007651305545650400164070ustar00rootroot00000000000000.. yaql documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to yaql documentation! ============================== .. toctree:: :maxdepth: 2 readme what_is_yaql getting_started usage .. toctree:: :maxdepth: 3 language_reference extending_yaql .. toctree:: standard_library .. toctree:: contributing yaql-1.1.3/doc/source/language_reference.rst000066400000000000000000000416031305545650400210750ustar00rootroot00000000000000Language reference ================== YAQL is a single expression language and as such does not have any block constructs, line formatting, end of statement marks or comments. The expression can be of any length. All whitespace characters (including newline) that are not enclosed in quote marks are stripped. Thus, the expressions may span multiple lines. Expressions consist of: * Literals * Keywords * Variable access * Function calls * Binary and unary operators * List expressions * Dictionary expressions * Index expressions * Delegate expressions Terminology ~~~~~~~~~~~ * `YAQL` - the name of the language - acronym for `Yet Another Query Language` * `yaql` - Python implementation of the YAQL language (this package) * `expression` - a YAQL query that takes context as an input and produces result value * `context` - an object that (directly or indirectly) holds all the data available to expression and all the function implementations accessible to expression * `host` - the application that hosts the yaql interpreter. The host uses yaql to evaluate expressions, provides initial data, and decides which functions are going to be available to the expression. The host has ultimate power to customize yaql - provide additional functions, operators, decide not to use standard library or use only parts of it, override function and operator behavior * `variable` - any data item that is available through the context * `function` - a Python callable that is exposed to the YAQL expression and can be called either explicitly or implicitly * `delegate` - a Python callable that is available as a context variable (in expression data rather than registered in context) * `operator` - a form of implicit function on one (unary operator) or two (binary operator) operands * `alphanumeric` - consists of latin letters and digits (`A-Z`, `a-z`, `0-9`) Literals ~~~~~~~~ Literals refer to fixed values in expressions. YAQL has the following literals: * Integer literals: ``123`` * Floating point literals: ``1.23``, ``1.0`` * Boolean and null literals represented by `keywords` (see below) * String literals enclosed in either single (') or double (") quotes: ``"abc"``, ``'def'``. The backslash (\) character is used to escape characters that otherwise have a special meaning, such as newline, backslash itself, or the quote character * Verbatim strings enclosed in back quote characters, for example ```abc```, are used to suppress escape sequences. This is equivalent to ``r'strings'`` in Python and is especially useful for regular expressions Keywords ~~~~~~~~ Keyword is a sequence of characters that conforms to the following criteria: * Consists of non-zero alphanumeric characters and an underscore (`_`) * Doesn't start with a digit * Doesn't start with two underscore characters (`__`) * Is not enclosed in quote marks of any type YAQL has only three predefined keywords: `true`, `false`, and `null` that have the value of similar JSON keywords. There are also four keyword operators: `and`, `or`, `not`, `in`. However, this list is not fixed. The yaql host may decide to have additional keyword operators or not to have any of the four aforementioned keywords. All other keywords have the value of their string representation. Thus, except for the predefined keywords and operators they can be considered as string literals and can be used anywhere where string is expected. However the opposite is not true. That is, keywords can be used as string literals but string literals cannot be used where a token is expected. Examples: * ``John + Snow`` - the same as ``"John" + "Snow"`` * ``true + love`` - syntactically valid, but cannot be evaluated because there is no plus operator that accepts boolean and string (unless you define one) * ``not true`` - evaluates to `false`, `not` is an operator * ``"foo"()`` - invalid expression because the function name must be a token * ``John Snow`` - invalid expression - two tokens with no operator between them Variable access ~~~~~~~~~~~~~~~ Each YAQL expression is a function that takes inputs (arguments) and produces the result value (usually by doing some computations on those inputs). Expressions get the input through a `context` - an object that holds all the data and a list of functions, available for expression. Besides the argument values, expressions may populate additional data items to the context. All these data are collectively known as a `variables` and available to all parts of an expression (unless overwritten with another value). The syntax for accessing variable values is ``$variableName`` where `variableName` is the name of the variable. Variable names may consist of alphanumeric and underscore characters only. Unlike tokens, variable names may start with digit, any number of underscores and even be an empty string. By convention, the first (usually the single) function parameter is accessible through ``$`` expression (i.e. empty string variable name) which is an alias for ``$1``. The usual case is to pass the main expression data in a single structure (document) and access it through the ``$`` variable. If the variable with given name is not provided, it is assumed to be `null`. There is no built-in syntax to check if a variable exists to distinguish cases where it does not and when it is just set to null. However in the future such a function might be added to yaql standard library. When the yaql parser encounters the ``$variable`` expression, it automatically translates it to the ``#get_context_data("$variable")`` function call. By default, the `#get_context_data` function returns a variable value from the current context. However the yaql host may decide to override it and provide another behavior. For example, the host may try to look up the value in an external data source (database) or throw an exception due to a missing variable. Function calls ~~~~~~~~~~~~~~ The power of YAQL comes from the fact that almost everything in YAQL is a function call (explicit or implicit) and any function may be overridden by the host. In YAQL there are two types of functions: * `explicit function` - those that can be called from expressions * `implicit (system) functions` - functions with predefined names that get called upon some operations. For example, ``2 + 3`` is translated to ``#operator_+(2, 3)``. In this case, `#operator_+` is the name of the implicit function. However, because ``#operator_+(2, 3)`` is not a valid YAQL expression (because of `#`), implicit functions cannot be called explicitly but still can be redefined by the host. The syntax for explicit function is: .. productionlist:: call: funcName "(" [parameters] ")" funcName: token parameters: positionalParameters | : keywordParameters | : positionalParameters "," keywordParameters positionalParameters: parameter ("," parameter)* parameter: expression | empty-string keywordParameters: keywordParameter ("," keywordParameter) keywordParameter: parameterName "=>" expression parameterName: token In simple words: * The function name must be a token. * Parameters may be positional, keyword or both. But keyword parameters may not come before positional. * Positional parameters can be skipped if they have a default value, for example, ``foo(1,,3)``. * Keyword arguments must have a token name that must match the parameter name in the function declaration. Therefore, you must know the function signature for the right name. Examples: * ``foo(2 + 3)`` * ``bar(hello, world)`` * ``baz(a,b, kwparam1 => c, kwparam2 => d)`` Functions have ultimate control over how they can be called. In particular: * Each parameter may (and usually does) have an associated type check. That is, the function may specify that the expected parameter type and if it can be null. * Usually, any parameters can be passed either by positional or keyword syntax. However, function declaration may force one particular way and make it positional-only or keyword-only. * A function may have a variable number of positional (aka `*args`) and/or keyword (aka `**kwarg`) arguments. * In most languages, function arguments are evaluated prior to function invocation. This is not always true in YAQL. In YAQL, a function may declare a lazy argument. In this case, it is not evaluated and the function implementation receives a passed value as a callable or even as an AST, depending on how the parameter was declared. Thus in YAQL there is no special syntax for lambdas. ``foo($ + 1)`` may mean either "call `foo` with value of ``$ + 1``" or "call `foo` with expression ``$ + 1`` as a parameter". In the latter case it corresponds to ``foo(lambda *args, **kwargs: args[0] + 1)`` in Python. Actual argument interpretation depends on the parameter declaration. * Function may decide to disable keyword argument syntax altogether. For such functions, the ``name => expr`` expression will be interpreted as a positional parameter ``yaql.language.utils.MappingRule(name, expr)`` and the left side of `=>` can be any expression and not just a keyword. This allows for functions like ``switch($ > 0 => 1, $ < 0 => -1, $ = 0 => 0)``. Additionally, there are three subtypes of explicit functions. Suppose that there is a declared function ``foo(string, int)``. By default, the syntax to call it will be ``foo(something, 123)``. But it can be declared as a `method`. In this case, the syntax is going to be ``something.foo(123)``. Because of the type checking, ``something.foo(123)`` will work since `something` is a string, but not the ``123.foo(456)``. Thus `foo` becomes a method of a string type. A function may also be declared as being an extension method. If foo were to be declared as an extension method it could be called both as a function (``foo(string, int)``) and as a method (``something.foo(123)``). YAQL makes use of a full function signature to determine which function implementation needs to be executed. This allows several overloads of the same function as long as they differ by parameter count or parameter type, or anything else that allows unambiguous identification of the right overload from the function call expression. For example, ``something.foo(123)`` may be resolved to a completely different implementation of `foo` from that in ``foo(something, 123)`` if there are two functions with the name `foo` present in the context, but one of them was declared as a function while the other as a method. If several overloads are equally suitable for the call expression, an `AmbiguousFunctionException` or `AmbiguousMethodException` exception gets raised. Operators ~~~~~~~~~ YAQL has both binary and unary operators, like most other languages do. Parentheses and `=>` sequence are not considered as operators and handled internally by the yaql parser. However, it is possible to configure yaql to use sequence other than `=>` for that purpose. The list of available operators is not fixed and can be modified by the host. The following operators are available by default: Binary operators: +--------------------------+---------------------------------+ | Group | Operators | +==========================+=================================+ | math operators | `+`, `-`, `*`, `/`, `mod` | +--------------------------+---------------------------------+ | comparision operators | `>`, `<`, `>=`, `<=`, `=`, `!=` | +--------------------------+---------------------------------+ | logical operators | `and`, `or` | +--------------------------+---------------------------------+ | method/member access | `.`, `?.` | +--------------------------+---------------------------------+ | regex operators | `=~`, `!~` | +--------------------------+---------------------------------+ | membership operator | `in` | +--------------------------+---------------------------------+ | context passing operator | `->` | +--------------------------+---------------------------------+ Unary operators: +--------------------------+---------------------------------+ | Group | Operators | +==========================+=================================+ | math operators | `+`, `-` | +--------------------------+---------------------------------+ | logical operators | `not` | +--------------------------+---------------------------------+ YAQL supports for both prefix and suffix unary operators. However, only the prefix operators are provided by default. In YAQL there are no built-in operators. The parser is given a list of all possible operator names (symbols), their associativity, precedence, and type, but it knows nothing about what operators are applicable for what operands. Each time a parser recognizes the ``X OP Y`` construct and `OP` is a known binary operator name, it translates the expression to ``#operator_OP(X, Y)``. Thus. ``2 + 3`` becomes ``#operator_+(2, 3)`` where `#operator_+` is an implicit function with several implementations including the one for number addition and defined in standard library. The host may override it and even completely disable it. For unary operators, ``OP X`` (or ``X OP`` for suffix unary operators) becomes ``#unary_operator_OP(X)``. Upon yaql parser initialization, an operator might be given an alias name. In such cases, ``X OP Y`` is translated to ``*ALIAS(X, Y)`` and ``OP X`` to ``*ALIAS(X)``. This decouples the operator implementation from the operator symbol. For example, the `=` operator has the `equal` alias. The host may configure yaql to have the `==` operator instead of `=` keeping the same alias so that operator implementation and all its consumers work equally well for the new operator symbol. In default configuration only `=` and `!=` operators have alias names. For information on default operators, see the YAQL standard library reference. List expressions ~~~~~~~~~~~~~~~~ List expressions have the following form: .. productionlist:: listExpression: "[" [expressions] "]" expressions: expression ("," expression)* When a yaql parser encounters an expression of the form ``[A, B, C]``, it translates it into ``#list(A, B, C)`` (for arbitrary number of arguments). Default `#list` function implementation in standard library produces a list (tuple) comprised of given elements. However, the host might decide to give it a different implementation. Map expressions ~~~~~~~~~~~~~~~ Map expressions have the following form: .. productionlist:: mapExpression: "{" [mappings] "}" mappings: mapping ("," mapping)* mapping: expression "=>" expression When a yaql parser encounters an expression of the form ``{A => X, B => Y}``, it translates it into ``#map(A => X, B => Y)``. The default `#map` implementation disables the keyword arguments syntax and thus receives a variable length list of mappings, which allows dictionary keys to be expressions rather than a keyword. It returns a (frozen) dictionary that itself can be used as a key in another map expression. For example, ``{{a => b} => {[2 + 2, 2 * 2] => 4}}`` is a valid YAQL expression though yaql REPL utility will fail to display its output due to the fact that it is not JSON-compatible. Index expressions ~~~~~~~~~~~~~~~~~ Index expressions have the following form: .. productionlist:: indexExpression: expression listExpression Examples: * ``[1, 2, 3][0]`` * ``$arr[$index + 1]`` * ``{foo => 1, bar => 2}[foo]`` When a yaql parser encounters such an expression, it translates it into ``#indexer(expression, index)``. The standard library provides a number of `#indexer` implementations for different types. The right side of the index expression is a list expression. Therefore, an expression like ``$foo[1, x, null]`` is also a valid YAQL expression and will be translated to ``#indexer($foo, 1, x, null)``. However, any attempt to evaluate such expression will result in `NoMatchingFunctionException` exception because there is no `#indexer` implementation that accepts such arguments (unless the host defines one). Delegate expressions ~~~~~~~~~~~~~~~~~~~~ Delegate expressions is an optional language feature that is disabled by default. It makes possible to pass delegates (callables) as part of the context data and invoke them from the expression. It has the same syntax as explicit function calls with the only difference being that instead of function name (keyword) there is a non-keyword expression that must produce the delegate. Examples: * ``$foo(1, arg => 2)`` - call delegate returned by ``$foo`` with parameters ``(1, arg => 2)`` * ``[$foo, $bar][0](x)`` - the same as ``$foo(x)`` * ``foo()()`` - can be written as ``(foo())()`` - ``foo()`` must return a delegate Delegate expressions are translated into ``#call(callable, arguments)``. Thus ``$foo(1, 2)`` becomes ``#call($foo, 1, 2)``. The default implementation of ``#call`` invokes the result of the evaluation of its first arguments with the given arguments. yaql-1.1.3/doc/source/readme.rst000066400000000000000000000000351305545650400165230ustar00rootroot00000000000000.. include:: ../../README.rstyaql-1.1.3/doc/source/standard_library.rst000066400000000000000000000021371305545650400206170ustar00rootroot00000000000000Standard YAQL Library ===================== Comparison operators ~~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.common Boolean logic functions ~~~~~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.boolean Working with collections ~~~~~~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.collections Querying data ~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.queries Branching functions ~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.branching String manipulations ~~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.strings Math functions ~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.math Regex functions ~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.regex DateTime functions ~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.date_time Intrinsic functions ~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.system YAQL`ization of Python classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.yaqlized Legacy YAQL compatibility functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. yaqldoc:: yaql.standard_library.legacy yaql-1.1.3/doc/source/usage.rst000066400000000000000000000001471305545650400163760ustar00rootroot00000000000000Usage ===== This section is not ready yet. Embedding YAQL ~~~~~~~~~~~~~~ REPL utility ~~~~~~~~~~~~ yaql-1.1.3/doc/source/what_is_yaql.rst000066400000000000000000000016441305545650400177610ustar00rootroot00000000000000What is YAQL ============ YAQL is a general purpose query language, that is designed to operate on objects of arbitrary complexity. YAQL has a large standard library of functions for filtering, grouping and aggregation of data. At the same time YAQL allows you to extend it by defining your own functions. Why YAQL? ========= So why bother and create another solution for a task, that has been addressed by many before us? Obviously because we were not satisfied with flexibility and/or quality of any existing solution. Most notably we needed a tool for json data, that would support some complex data transformations. YAQL is a pure-python library and therefore is easily embeddable in any python application. YAQL is designed to be human-readable and has a SQL-like feel and look. It is inspired in part by LINQ for .NET. Since YAQL is extensible and embeddable it makes a perfect choice for becoming the basis for your DSLs. yaql-1.1.3/releasenotes/000077500000000000000000000000001305545650400151625ustar00rootroot00000000000000yaql-1.1.3/releasenotes/notes/000077500000000000000000000000001305545650400163125ustar00rootroot00000000000000yaql-1.1.3/releasenotes/notes/.placeholder000066400000000000000000000000001305545650400205630ustar00rootroot00000000000000yaql-1.1.3/releasenotes/source/000077500000000000000000000000001305545650400164625ustar00rootroot00000000000000yaql-1.1.3/releasenotes/source/_static/000077500000000000000000000000001305545650400201105ustar00rootroot00000000000000yaql-1.1.3/releasenotes/source/_static/.placeholder000066400000000000000000000000001305545650400223610ustar00rootroot00000000000000yaql-1.1.3/releasenotes/source/_templates/000077500000000000000000000000001305545650400206175ustar00rootroot00000000000000yaql-1.1.3/releasenotes/source/_templates/.placeholder000066400000000000000000000000001305545650400230700ustar00rootroot00000000000000yaql-1.1.3/releasenotes/source/conf.py000066400000000000000000000217171305545650400177710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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. # YAQL Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'oslosphinx', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'YAQL Release Notes' copyright = u'2016, YAQL Developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. from yaql import __version__ as yaql_version # The full version, including alpha/beta/rc tags. # release = yaql_version.version_string_with_vcs() # The short X.Y version. # version = yaql_version.canonical_version_string() relese = version = yaql_version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'YaqlReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'YaqlReleaseNotes.tex', u'Yaql Release Notes Documentation', u'Yaql Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'yaqlreleasenotes', u'Yaql Release Notes Documentation', [u'Yaql Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'YaqlReleaseNotes', u'Yaql Release Notes Documentation', u'Yaql Developers', 'YaqlReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] yaql-1.1.3/releasenotes/source/index.rst000066400000000000000000000002001305545650400203130ustar00rootroot00000000000000==================== YAQL Release Notes ==================== .. toctree:: :maxdepth: 2 unreleased mitaka liberty yaql-1.1.3/releasenotes/source/liberty.rst000066400000000000000000000002221305545650400206620ustar00rootroot00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: origin/stable/liberty yaql-1.1.3/releasenotes/source/mitaka.rst000066400000000000000000000002321305545650400204570ustar00rootroot00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka yaql-1.1.3/releasenotes/source/unreleased.rst000066400000000000000000000001601305545650400213400ustar00rootroot00000000000000============================== Current Series Release Notes ============================== .. release-notes:: yaql-1.1.3/requirements.txt000066400000000000000000000000721305545650400157540ustar00rootroot00000000000000pbr>=1.8 Babel>=1.3 python-dateutil>=2.4.2 ply six>=1.9.0 yaql-1.1.3/setup.cfg000066400000000000000000000021301305545650400143060ustar00rootroot00000000000000[metadata] name = yaql summary = YAQL - Yet Another Query Language description-file = README.rst author = Stan Lagun author-email = slagun@mirantis.com home-page = http://yaql.readthedocs.io classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 [files] packages = yaql [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = yaql/locale domain = yaql [update_catalog] domain = yaql output_dir = yaql/locale input_file = yaql/locale/yaql.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = yaql/locale/yaql.pot [entry_points] console_scripts = yaql = yaql.cli.run:main yaql-1.1.3/setup.py000066400000000000000000000014151305545650400142040ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr'], pbr=True) yaql-1.1.3/test-requirements.txt000066400000000000000000000003351305545650400167330ustar00rootroot00000000000000hacking>=0.12.0,!=0.13.0,<0.14 coverage>=3.6 fixtures>=1.3.1 python-subunit>=0.0.18 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 oslosphinx>=2.5.0 testrepository>=0.0.18 testscenarios>=0.4 testtools>=1.4.0 reno>=1.8.0 # Apache2 yaql-1.1.3/tox.ini000066400000000000000000000020661305545650400140100ustar00rootroot00000000000000[tox] minversion = 1.6 envlist = py27,py34,pypy,pep8 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' [testenv:docs] commands = python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] # H803 skipped on purpose per list discussion. # H404 multi line docstring should start with a summary # H405 multi line docstring summary not separated with an empty line ## TODO(ruhe) following checks should be fixed # E721 do not compare types, use 'isinstance()' show-source = True ignore = E721,H404,H405,H803 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build yaql-1.1.3/yaql/000077500000000000000000000000001305545650400134375ustar00rootroot00000000000000yaql-1.1.3/yaql/__init__.py000066400000000000000000000113171305545650400155530ustar00rootroot00000000000000# 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 os.path import pkg_resources from yaql.language import contexts from yaql.language import conventions from yaql.language import factory from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes from yaql.standard_library import boolean as std_boolean from yaql.standard_library import branching as std_branching from yaql.standard_library import collections as std_collections from yaql.standard_library import common as std_common from yaql.standard_library import date_time as std_datetime from yaql.standard_library import math as std_math from yaql.standard_library import queries as std_queries from yaql.standard_library import regex as std_regex from yaql.standard_library import strings as std_strings from yaql.standard_library import system as std_system from yaql.standard_library import yaqlized as std_yaqlized _cached_expressions = {} _cached_engine = None _default_context = None def detect_version(): try: dist = pkg_resources.get_distribution('yaql') location = os.path.normcase(dist.location) this_location = os.path.normcase(__file__) if not this_location.startswith(os.path.join(location, 'yaql')): raise pkg_resources.DistributionNotFound() return dist.version except pkg_resources.DistributionNotFound: return 'Undefined (package was not installed with setuptools)' __version__ = detect_version() def _setup_context(data, context, finalizer, convention): if context is None: context = contexts.Context( convention=convention or conventions.CamelCaseConvention()) if finalizer is None: @specs.parameter('iterator', yaqltypes.Iterable()) @specs.name('#iter') def limit(iterator): return iterator @specs.inject('limiter', yaqltypes.Delegate('#iter')) @specs.inject('engine', yaqltypes.Engine()) @specs.name('#finalize') def finalize(obj, limiter, engine): if engine.options.get('yaql.convertOutputData', True): return utils.convert_output_data(obj, limiter, engine) return obj context.register_function(limit) context.register_function(finalize) else: context.register_function(finalizer) if data is not utils.NO_VALUE: context['$'] = utils.convert_input_data(data) return context def create_context(data=utils.NO_VALUE, context=None, system=True, common=True, boolean=True, strings=True, math=True, collections=True, queries=True, regex=True, branching=True, no_sets=False, finalizer=None, delegates=False, convention=None, datetime=True, yaqlized=True): context = _setup_context(data, context, finalizer, convention) if system: std_system.register_fallbacks(context) context = context.create_child_context() std_system.register(context, delegates) if common: std_common.register(context) if boolean: std_boolean.register(context) if strings: std_strings.register(context) if math: std_math.register(context) if collections: std_collections.register(context, no_sets) if queries: std_queries.register(context) if regex: std_regex.register(context) if branching: std_branching.register(context) if datetime: std_datetime.register(context) if yaqlized: context = std_yaqlized.register(context) return context YaqlFactory = factory.YaqlFactory def eval(expression, data=None): global _cached_engine, _cached_expressions, _default_context if _cached_engine is None: _cached_engine = YaqlFactory().create() parsed_expression = _cached_expressions.get(expression) if parsed_expression is None: parsed_expression = _cached_engine(expression) _cached_expressions[expression] = parsed_expression if _default_context is None: _default_context = create_context() return parsed_expression.evaluate( data=data, context=_default_context.create_child_context()) yaql-1.1.3/yaql/cli/000077500000000000000000000000001305545650400142065ustar00rootroot00000000000000yaql-1.1.3/yaql/cli/__init__.py000066400000000000000000000000001305545650400163050ustar00rootroot00000000000000yaql-1.1.3/yaql/cli/cli_functions.py000066400000000000000000000104201305545650400174140ustar00rootroot00000000000000# Copyright (c) 2013-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. from __future__ import print_function import json import locale import os import readline import sys import six from yaql.language.exceptions import YaqlParsingException from yaql import __version__ as version from yaql.language import utils PROMPT = "yaql> " def main(context, show_tokens, parser): print("Yet Another Query Language - command-line query tool") print("Version {0}".format(version)) if context.get_data('legacy', False): print("Running in a legacy (0.2.x compatible) mode") print("Copyright (c) 2013-2017 Mirantis, Inc") print("") if not context['']: print("No data loaded into context ") print("Type '@load data-file.json' to load data") print("") readline.parse_and_bind('') comm = True while comm != 'exit': try: comm = six.moves.input(PROMPT) if six.PY2: comm = comm.decode( sys.stdin.encoding or locale.getpreferredencoding(True)) except EOFError: return if not comm: continue if comm[0] == '@': func_name, args = parse_service_command(comm) if func_name not in SERVICE_FUNCTIONS: print('Unknown command ' + func_name) else: SERVICE_FUNCTIONS[func_name](args, context) continue try: if show_tokens: parser.lexer.input(comm) tokens = [] while True: tok = parser.lexer.token() if not tok: break tokens.append(tok) print('Tokens: ' + str(tokens)) expr = parser(comm) if show_tokens: print('Expression: ' + str(expr)) except YaqlParsingException as ex: if ex.position: pointer_string = (" " * (ex.position + len(PROMPT))) + '^' print(pointer_string) print(ex.message) continue try: res = expr.evaluate(context=context) print_output(res, context) except Exception as ex: print(u'Execution exception: {0}'.format(ex), file=sys.stderr) def load_data(data_file, context): try: json_str = open(os.path.expanduser(data_file)).read() except IOError as e: print("Unable to read data file '{0}': {1}".format(data_file, e.strerror)) return try: data = json.loads(json_str) except Exception as e: print('Unable to parse data: ' + e.message) return context['$'] = utils.convert_input_data(data) print('Data from file {0} loaded into context'.format(data_file)) def register_in_context(context, parser): context.register_function( lambda context, show_tokens: main(context, show_tokens, parser), name='__main') def parse_service_command(comm): space_index = comm.find(' ') if space_index == -1: return comm, None func_name = comm[:space_index] args = comm[len(func_name) + 1:] return func_name, args def evaluate(expr, parser, data, context): try: res = parser(expr).evaluate(data, context) print_output(res, context) except Exception as ex: print(u'Execution exception: {0}'.format(ex), file=sys.stderr) exit(1) def print_output(v, context): if context['#nativeOutput']: print(v) else: print(json.dumps(v, indent=4, ensure_ascii=False)) SERVICE_FUNCTIONS = { # '@help':print_help, '@load': load_data, # '@import':import_module, # '@register':register_function } yaql-1.1.3/yaql/cli/run.py000077500000000000000000000057611305545650400154000ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (c) 2013-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. from __future__ import print_function import json import optparse import sys import yaql from yaql.cli import cli_functions import yaql.legacy def read_data(f, options): if options.string: if options.array: return [line.rstrip('\n') for line in f] else: return f.read() else: if options.array: return [json.loads(s) for s in f.readlines()] else: return json.load(f) def main(): p = optparse.OptionParser() p.add_option('--data', '-d', help="input file") p.add_option('--string', '-s', action='store_true', help="input is a string") p.add_option('--native', '-n', action='store_true', help="output data in Python native format") p.add_option('--array', '-a', action='store_true', help="read input line by line") p.add_option('--tokens', '-t', action='store_true', dest='tokens', help="print lexical tokens info") p.add_option('--legacy', action='store_true', dest='legacy', help="enable legacy v0.2 compatibility mode") options, arguments = p.parse_args() if options.data: try: if options.data == '-': data = read_data(sys.stdin, options) else: with open(options.data) as f: data = read_data(f, options) except Exception: print('Unable to load data from ' + options.data, file=sys.stderr) exit(1) else: data = None engine_options = { 'yaql.limitIterators': 1000, 'yaql.convertSetsToLists': True, 'yaql.memoryQuota': 100000 } if options.legacy: factory = yaql.legacy.YaqlFactory() context = yaql.legacy.create_context() context['legacy'] = True else: factory = yaql.YaqlFactory() context = yaql.create_context() if options.native: context['#nativeOutput'] = True parser = factory.create(options=engine_options) cli_functions.register_in_context(context, parser) if len(arguments) > 0: for arg in arguments: cli_functions.evaluate(arg, parser, data, context) elif options.tokens: parser('__main(true)').evaluate(data, context) else: parser('__main(false)').evaluate(data, context) if __name__ == "__main__": main() yaql-1.1.3/yaql/contrib/000077500000000000000000000000001305545650400150775ustar00rootroot00000000000000yaql-1.1.3/yaql/contrib/README.rst000066400000000000000000000052521305545650400165720ustar00rootroot00000000000000YAQL documenter script ====================== YAQL documenter is a script which collects docstrings from given Python package or module which contains YAQL functions which is properly documented. It is created to document standard library functions, but also it can be used to document custom YAQL functions if they follow documentation guideline for YAQL. Documentation guideline ----------------------- 1. Provide a description for module itself, or module will be marked as ``not documented yet``. For example: """ Whenever an expression is used in the context of boolean operations the following values are interpreted as false: ``false``, ``none``, numeric zero of any type, empty strings, empty dict, empty list. All other values are interpreted as true. """ 2. YAQL related part of docstring should starts with marker ``:yaql:``. 3. After marker you should first specify a name for function in YAQL. For example `operator and`. 4. Next string should contain function description. For example: `Returns left operand if it evaluates to false, otherwise right.` 5. After that you can specify its signature using `:signature:`, args, args type and code description if you want. 6. Documenter will automatically add `:callAs:` field with value among `operator`, `method`, `function`, `function or method`. Complete example of documented function --------------------------------------- .. code:: @specs.parameter('left', yaqltypes.Lambda()) @specs.parameter('right', yaqltypes.Lambda()) @specs.name('#operator_and') def and_(left, right): """:yaql:operator and Returns left operand if it evaluates to false, otherwise right. :usage: left and right :arg left: left operand :argType left: any :arg right: right operand :argType right: any .. code:: yaql> 1 and 0 0 yaql> 1 and 2 2 yaql> [] and 1 [] """ return left() and right() .. Documenter script workflow -------------------------- To add your documentation for YAQL functions you should run documenter script from the environment where Python package or module contains the functions to document. In case if you document package other than standard library YAQL it isn't needed. You can use virtualenv, install it locally or whatever you want. After that run documenter with the following: .. console:: python yaql/contrib/documenter.py --output .. Here is an example for the default workflow: .. console:: python yaql/contrib/documenter.py --output doc/source/standard_library.rst yaql.standard_library .. yaql-1.1.3/yaql/language/000077500000000000000000000000001305545650400152225ustar00rootroot00000000000000yaql-1.1.3/yaql/language/__init__.py000066400000000000000000000000001305545650400173210ustar00rootroot00000000000000yaql-1.1.3/yaql/language/contexts.py000066400000000000000000000234331305545650400174500ustar00rootroot00000000000000# Copyright (c) 2013 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import six from yaql.language import exceptions from yaql.language import runner from yaql.language import specs from yaql.language import utils @six.add_metaclass(abc.ABCMeta) class ContextBase(object): def __init__(self, parent_context=None, convention=None): self._parent_context = parent_context self._convention = convention if convention is None and parent_context: self._convention = parent_context.convention @property def parent(self): return self._parent_context @abc.abstractmethod def register_function(self, spec, *args, **kwargs): pass @abc.abstractmethod def get_data(self, name, default=None, ask_parent=True): return default def __getitem__(self, name): return self.get_data(name) @abc.abstractmethod def __setitem__(self, name, value): pass @abc.abstractmethod def __delitem__(self, name): pass @abc.abstractmethod def __contains__(self, item): return False def __call__(self, name, engine, receiver=utils.NO_VALUE, data_context=None, use_convention=False, function_filter=None): return lambda *args, **kwargs: runner.call( name, self, args, kwargs, engine, receiver, data_context, use_convention, function_filter) @abc.abstractmethod def get_functions(self, name, predicate=None, use_convention=False): return [], False @abc.abstractmethod def delete_function(self, spec): pass def collect_functions(self, name, predicate=None, use_convention=False): overloads = [] p = self while p is not None: context_predicate = None if predicate: context_predicate = lambda fd: predicate(fd, p) layer_overloads, is_exclusive = p.get_functions( name, context_predicate, use_convention) p = None if is_exclusive else p.parent if layer_overloads: overloads.append(layer_overloads) return overloads def create_child_context(self): return type(self)(self) @property def convention(self): return self._convention @abc.abstractmethod def keys(self): return six.iterkeys({}) class Context(ContextBase): def __init__(self, parent_context=None, data=utils.NO_VALUE, convention=None): super(Context, self).__init__(parent_context, convention) self._functions = {} self._data = {} self._exclusive_funcs = set() if data is not utils.NO_VALUE: self['$'] = data @staticmethod def _import_function_definition(fd): return fd def register_function(self, spec, *args, **kwargs): exclusive = kwargs.pop('exclusive', False) if not isinstance(spec, specs.FunctionDefinition) \ and six.callable(spec): spec = specs.get_function_definition( spec, *args, convention=self._convention, **kwargs) spec = self._import_function_definition(spec) if spec.is_method: if not spec.is_valid_method(): raise exceptions.InvalidMethodException(spec.name) self._functions.setdefault(spec.name, set()).add(spec) if exclusive: self._exclusive_funcs.add(spec.name) def delete_function(self, spec): self._functions.get(spec.name, set()).discard(spec) self._exclusive_funcs.discard(spec.name) def get_functions(self, name, predicate=None, use_convention=False): name = name.rstrip('_') if use_convention and self._convention is not None: name = self._convention.convert_function_name(name) if predicate is None: predicate = lambda x: True return ( set(six.moves.filter(predicate, self._functions.get(name, set()))), name in self._exclusive_funcs ) @staticmethod def _normalize_name(name): if not name.startswith('$'): name = ('$' + name) if name == '$': name = '$1' return name def __setitem__(self, name, value): self._data[self._normalize_name(name)] = value def get_data(self, name, default=None, ask_parent=True): name = self._normalize_name(name) if name in self._data: return self._data[name] ctx = self.parent while ask_parent and ctx: result = ctx.get_data(name, utils.NO_VALUE, False) if result is utils.NO_VALUE: ctx = ctx.parent else: return result return default def __delitem__(self, name): self._data.pop(self._normalize_name(name)) def __contains__(self, item): if isinstance(item, specs.FunctionDefinition): return item in self._functions.get(item.name, []) if isinstance(item, six.string_types): return self._normalize_name(item) in self._data return False def keys(self): return six.iterkeys(self._data) class MultiContext(ContextBase): def __init__(self, context_list, convention=None): self._context_list = context_list if convention is None: convention = context_list[0].convention parents = tuple(six.moves.filter( lambda t: t, six.moves.map(lambda t: t.parent, context_list))) if not parents: super(MultiContext, self).__init__(None, convention) elif len(parents) == 1: super(MultiContext, self).__init__(parents[0], convention) else: super(MultiContext, self).__init__(MultiContext(parents), convention) def register_function(self, spec, *args, **kwargs): self._context_list[0].register_function(spec, *args, **kwargs) def get_data(self, name, default=None, ask_parent=True): for context in self._context_list: result = context.get_data(name, utils.NO_VALUE, False) if result is not utils.NO_VALUE: return result ctx = self.parent while ask_parent and ctx: result = ctx.get_data(name, utils.NO_VALUE, False) if result is utils.NO_VALUE: ctx = ctx.parent else: return result return default def __setitem__(self, name, value): self._context_list[0][name] = value def __delitem__(self, name): for context in self._context_list: del context[name] def create_child_context(self): return Context(self) def keys(self): prev_keys = set() for context in self._context_list: for key in context.keys(): if key not in prev_keys: prev_keys.add(key) yield key def delete_function(self, spec): for context in self._context_list: context.delete_function(spec) def __contains__(self, item): for context in self._context_list: if item in context: return True return False def get_functions(self, name, predicate=None, use_convention=False): result = set() is_exclusive = False for context in self._context_list: funcs, exclusive = context.get_functions( name, predicate, use_convention) result.update(funcs) if exclusive: is_exclusive = True return result, is_exclusive class LinkedContext(ContextBase): """Context that is as a proxy to another context but has its own parent.""" def __init__(self, parent_context, linked_context, convention=None): self.linked_context = linked_context if linked_context.parent: super(LinkedContext, self).__init__( LinkedContext(parent_context, linked_context.parent, convention), convention) else: super(LinkedContext, self).__init__(parent_context, convention) def register_function(self, spec, *args, **kwargs): return self.linked_context.register_function(spec, *args, **kwargs) def keys(self): return self.linked_context.keys() def get_data(self, name, default=None, ask_parent=True): result = self.linked_context.get_data( name, default=utils.NO_VALUE, ask_parent=False) if result is utils.NO_VALUE: if not ask_parent or not self.parent: return default return self.parent.get_data(name, default=default, ask_parent=True) return result def get_functions(self, name, predicate=None, use_convention=False): return self.linked_context.get_functions( name, predicate=predicate, use_convention=use_convention) def delete_function(self, spec): return self.linked_context.delete_function(spec) def __contains__(self, item): return item in self.linked_context def __delitem__(self, name): del self.linked_context[name] def __setitem__(self, name, value): self.linked_context[name] = value def create_child_context(self): return type(self.linked_context)(self) yaql-1.1.3/yaql/language/conventions.py000066400000000000000000000026371305545650400201510ustar00rootroot00000000000000# 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 re import six @six.add_metaclass(abc.ABCMeta) class Convention(object): @abc.abstractmethod def convert_function_name(self, name): pass @abc.abstractmethod def convert_parameter_name(self, name): pass class PythonConvention(Convention): def convert_function_name(self, name): return name def convert_parameter_name(self, name): return name class CamelCaseConvention(Convention): def __init__(self): self.regex = re.compile(r'(?!^)_(\w)', flags=re.UNICODE) def convert_function_name(self, name): return self._to_camel_case(name) def convert_parameter_name(self, name): return self._to_camel_case(name) def _to_camel_case(self, name): return self.regex.sub(lambda m: m.group(1).upper(), name) yaql-1.1.3/yaql/language/exceptions.py000066400000000000000000000124031305545650400177550ustar00rootroot00000000000000# 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. class YaqlException(Exception): pass class ResolutionError(YaqlException): pass class FunctionResolutionError(ResolutionError): pass class MethodResolutionError(ResolutionError): pass class NoFunctionRegisteredException(FunctionResolutionError): def __init__(self, name): super(NoFunctionRegisteredException, self).__init__( u'Unknown function "{0}"'.format(name)) class NoMethodRegisteredException(MethodResolutionError): def __init__(self, name, receiver): super(NoMethodRegisteredException, self).__init__( u'Unknown method "{0}" for receiver {1}'.format(name, receiver)) class NoMatchingFunctionException(FunctionResolutionError): def __init__(self, name): super(NoMatchingFunctionException, self).__init__( u'No function "{0}" matches supplied arguments'.format(name)) class NoMatchingMethodException(MethodResolutionError): def __init__(self, name, receiver): super(NoMatchingMethodException, self).__init__( u'No method "{0}" for receiver {1} matches ' u'supplied arguments'.format(name, receiver)) class AmbiguousFunctionException(FunctionResolutionError): def __init__(self, name): super(AmbiguousFunctionException, self).__init__( u'Ambiguous function "{0}"'.format(name)) class AmbiguousMethodException(MethodResolutionError): def __init__(self, name, receiver): super(AmbiguousMethodException, self).__init__( u'Ambiguous method "{0}" for receiver {1}'.format(name, receiver)) class ArgumentException(YaqlException): def __init__(self, argument_name): self.parameter_name = argument_name super(ArgumentException, self).__init__( u'Invalid argument {0}'.format(argument_name)) class MappingTranslationException(YaqlException): def __init__(self): super(MappingTranslationException, self).__init__( u'Cannot convert mapping to keyword argument') class ArgumentValueException(YaqlException): def __init__(self): super(ArgumentValueException, self).__init__() class DuplicateParameterDecoratorException(YaqlException): def __init__(self, function_name, param_name): message = u"Function '{0}' has multiple " \ u"decorators for parameter '{1}'". \ format(function_name, param_name) super(DuplicateParameterDecoratorException, self).__init__(message) class InvalidMethodException(YaqlException): def __init__(self, function_name): message = u"Function '{0}' cannot be called as a method". \ format(function_name) super(InvalidMethodException, self).__init__(message) class NoParameterFoundException(YaqlException): def __init__(self, function_name, param_name): message = u"Function '{0}' has no parameter called '{1}'". \ format(function_name, param_name) super(NoParameterFoundException, self).__init__(message) class YaqlParsingException(YaqlException): def __init__(self, value, position, message): self.value = value self.position = position self.message = message super(YaqlParsingException, self).__init__(message) class YaqlGrammarException(YaqlParsingException): def __init__(self, expr, value, position): if position is None: msg = u'Parse error: unexpected end of statement' else: msg = u"Parse error: unexpected '{0}' at position {1} of " \ u"expression '{2}'".format(value, position, expr) super(YaqlGrammarException, self).__init__(value, position, msg) class YaqlLexicalException(YaqlParsingException): def __init__(self, value, position): msg = u"Lexical error: illegal character '{0}' at position {1}" \ .format(value, position) super(YaqlLexicalException, self).__init__(value, position, msg) class InvalidOperatorTableException(YaqlException): def __init__(self, op): super(InvalidOperatorTableException, self). \ __init__(u"Invalid records in operator table for operator " u"'{0}".format(op)) class WrappedException(YaqlException): def __init__(self, exception): self.wrapped = exception super(WrappedException, self).__init__(str(exception)) class CollectionTooLargeException(YaqlException): def __init__(self, count): super(CollectionTooLargeException, self).__init__( 'Collection length exceeds {0} elements'.format(count)) class MemoryQuotaExceededException(YaqlException): def __init__(self): super(MemoryQuotaExceededException, self).__init__( 'Expression consumed too much memory') yaql-1.1.3/yaql/language/expressions.py000066400000000000000000000120531305545650400201570ustar00rootroot00000000000000# 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 sys import six import yaql from yaql.language import exceptions from yaql.language import utils class Expression(object): def __call__(self, receiver, context, engine): pass @six.python_2_unicode_compatible class Function(Expression): def __init__(self, name, *args): self.name = name self.args = args self.uses_receiver = True def __call__(self, receiver, context, engine): return context(self.name, engine, receiver, context)(*self.args) def __str__(self): return u'{0}({1})'.format(self.name, ', '.join( map(six.text_type, self.args))) class BinaryOperator(Function): def __init__(self, op, obj1, obj2, alias): if alias is None: func_name = '#operator_' + op else: func_name = '*' + alias self.operator = op super(BinaryOperator, self).__init__(func_name, obj1, obj2) self.uses_receiver = False class UnaryOperator(Function): def __init__(self, op, obj, alias): if alias is None: func_name = '#unary_operator_' + op else: func_name = '*' + alias self.operator = op super(UnaryOperator, self).__init__(func_name, obj) self.uses_receiver = False class IndexExpression(Function): def __init__(self, value, *args): super(IndexExpression, self).__init__('#indexer', value, *args) self.uses_receiver = False class ListExpression(Function): def __init__(self, *args): super(ListExpression, self).__init__('#list', *args) self.uses_receiver = False class MapExpression(Function): def __init__(self, *args): super(MapExpression, self).__init__('#map', *args) self.uses_receiver = False @six.python_2_unicode_compatible class GetContextValue(Function): def __init__(self, path): super(GetContextValue, self).__init__('#get_context_data', path) self.path = path self.uses_receiver = False def __str__(self): return self.path.value @six.python_2_unicode_compatible class Constant(Expression): def __init__(self, value): self.value = value self.uses_receiver = False def __str__(self): if isinstance(self.value, six.text_type): return u"'{0}'".format(self.value) return six.text_type(self.value) def __call__(self, receiver, context, engine): return self.value class KeywordConstant(Constant): pass @six.python_2_unicode_compatible class Wrap(Expression): def __init__(self, expression): self.expr = expression self.uses_receiver = False def __str__(self): return str(self.expr) def __call__(self, receiver, context, engine): return self.expr(receiver, context, engine) @six.python_2_unicode_compatible class MappingRuleExpression(Expression): def __init__(self, source, destination): self.source = source self.destination = destination self.uses_receiver = False def __str__(self): return u'{0} => {1}'.format(self.source, self.destination) def __call__(self, receiver, context, engine): return utils.MappingRule( self.source(receiver, context, engine), self.destination(receiver, context, engine)) @six.python_2_unicode_compatible class Statement(Function): def __init__(self, expression, engine): self.expression = expression self.uses_receiver = False self.engine = engine super(Statement, self).__init__('#finalize', expression) def __call__(self, receiver, context, engine): if not context.collect_functions('#finalize'): context = context.create_child_context() context.register_function(lambda x: x, name='#finalize') try: return super(Statement, self).__call__(receiver, context, engine) except exceptions.WrappedException as e: six.reraise(type(e.wrapped), e.wrapped, sys.exc_info()[2]) def evaluate(self, data=utils.NO_VALUE, context=None): if context is None or context is utils.NO_VALUE: context = yaql.create_context() if data is not utils.NO_VALUE: if self.engine.options.get('yaql.convertInputData', True): context['$'] = utils.convert_input_data(data) else: context['$'] = data return self(utils.NO_VALUE, context, self.engine) def __str__(self): return str(self.expression) yaql-1.1.3/yaql/language/factory.py000066400000000000000000000215461305545650400172530ustar00rootroot00000000000000# 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 collections import re import uuid from ply import lex from ply import yacc from yaql.language import exceptions from yaql.language import expressions from yaql.language import lexer from yaql.language import parser from yaql.language import utils OperatorType = collections.namedtuple('OperatorType', [ 'PREFIX_UNARY', 'SUFFIX_UNARY', 'BINARY_LEFT_ASSOCIATIVE', 'BINARY_RIGHT_ASSOCIATIVE', 'NAME_VALUE_PAIR' ])( PREFIX_UNARY='PREFIX_UNARY', SUFFIX_UNARY='SUFFIX_UNARY', BINARY_LEFT_ASSOCIATIVE='BINARY_LEFT_ASSOCIATIVE', BINARY_RIGHT_ASSOCIATIVE='BINARY_RIGHT_ASSOCIATIVE', NAME_VALUE_PAIR='NAME_VALUE_PAIR' ) class YaqlOperators(object): def __init__(self, operators, name_value_op=None): self.operators = operators self.name_value_op = name_value_op class YaqlEngine(object): def __init__(self, ply_lexer, ply_parser, options, factory): self._lexer = ply_lexer self._parser = ply_parser self._options = utils.FrozenDict(options or {}) self._factory = factory @property def lexer(self): return self._lexer @property def parser(self): return self._parser @property def options(self): return self._options @property def factory(self): return self._factory def __call__(self, expression, options=None): if options: return self.copy(options)(expression) return expressions.Statement( self.parser.parse(expression, lexer=self.lexer), self) def copy(self, options): opt = dict(self._options) opt.update(options) return YaqlEngine(self._lexer, self._parser, opt, self._factory) class YaqlFactory(object): def __init__(self, keyword_operator='=>', allow_delegates=False): self._keyword_operator = keyword_operator self._allow_delegates = allow_delegates self.operators = self._standard_operators() if keyword_operator: self.operators.insert(0, (keyword_operator, OperatorType.NAME_VALUE_PAIR)) @property def keyword_operator(self): return self._keyword_operator @property def allow_delegates(self): return self._allow_delegates # noinspection PyMethodMayBeStatic def _standard_operators(self): return [ ('.', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('?.', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('[]', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('{}', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('+', OperatorType.PREFIX_UNARY), ('-', OperatorType.PREFIX_UNARY), (), ('=~', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('!~', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('*', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('/', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('mod', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('+', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('-', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('>', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('<', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('>=', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('<=', OperatorType.BINARY_LEFT_ASSOCIATIVE), ('!=', OperatorType.BINARY_LEFT_ASSOCIATIVE, 'not_equal'), ('=', OperatorType.BINARY_LEFT_ASSOCIATIVE, 'equal'), ('in', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('not', OperatorType.PREFIX_UNARY), (), ('and', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('or', OperatorType.BINARY_LEFT_ASSOCIATIVE), (), ('->', OperatorType.BINARY_RIGHT_ASSOCIATIVE), ] def insert_operator(self, existing_operator, existing_operator_binary, new_operator, new_operator_type, create_group, new_operator_alias=None): binary_types = (OperatorType.BINARY_RIGHT_ASSOCIATIVE, OperatorType.BINARY_LEFT_ASSOCIATIVE) unary_types = (OperatorType.PREFIX_UNARY, OperatorType.SUFFIX_UNARY) position = 0 if existing_operator is not None: position = -1 for i, t in enumerate(self.operators): if len(t) < 2 or t[0] != existing_operator: continue if existing_operator_binary and t[1] not in binary_types: continue if not existing_operator_binary and t[1] not in unary_types: continue position = i break if position < 0: raise ValueError('Operator {0} is not found'.format( existing_operator)) while position < len(self.operators) and len( self.operators[position]) > 1: position += 1 if create_group: if position == len(self.operators): self.operators.append(()) position += 1 else: while position < len(self.operators) and len( self.operators[position]) < 2: position += 1 self.operators.insert(position, ()) self.operators.insert( position, (new_operator, new_operator_type, new_operator_alias)) @staticmethod def _name_generator(): value = 1 while True: t = value chars = [] while t: chars.append(chr(ord('A') + t % 26)) t //= 26 yield ''.join(chars) value += 1 def _build_operator_table(self, name_generator): operators = {} name_value_op = None precedence = 1 for record in self.operators: if not record: precedence += 1 continue up, bp, name, alias = operators.get(record[0], (0, 0, '', None)) if record[1] == OperatorType.NAME_VALUE_PAIR: if name_value_op is not None: raise exceptions.InvalidOperatorTableException(record[0]) name_value_op = record[0] continue if record[1] == OperatorType.PREFIX_UNARY: if up: raise exceptions.InvalidOperatorTableException(record[0]) up = precedence elif record[1] == OperatorType.SUFFIX_UNARY: if up: raise exceptions.InvalidOperatorTableException(record[0]) up = -precedence elif record[1] == OperatorType.BINARY_LEFT_ASSOCIATIVE: if bp: raise exceptions.InvalidOperatorTableException(record[0]) bp = precedence elif record[1] == OperatorType.BINARY_RIGHT_ASSOCIATIVE: if bp: raise exceptions.InvalidOperatorTableException(record[0]) bp = -precedence if record[0] == '[]': name = 'INDEXER' elif record[0] == '{}': name = 'MAP' else: name = name or 'OP_' + next(name_generator) operators[record[0]] = ( up, bp, name, record[2] if len(record) > 2 else None) return YaqlOperators(operators, name_value_op) # noinspection PyMethodMayBeStatic def _create_lexer(self, operators): return lexer.Lexer(operators) # noinspection PyMethodMayBeStatic def _create_parser(self, lexer_rules, operators): return parser.Parser(lexer_rules, operators, self) def create(self, options=None): names = self._name_generator() operators = self._build_operator_table(names) lexer_rules = self._create_lexer(operators) ply_lexer = lex.lex(object=lexer_rules, reflags=re.UNICODE | re.VERBOSE) ply_parser = yacc.yacc( module=self._create_parser(lexer_rules, operators), debug=False if not options else options.get('yaql.debug', False), tabmodule='m' + uuid.uuid4().hex, write_tables=False) return YaqlEngine(ply_lexer, ply_parser, options, self) yaql-1.1.3/yaql/language/lexer.py000066400000000000000000000076551305545650400167300ustar00rootroot00000000000000# 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 codecs import re import six from yaql.language import exceptions NEVER_MATCHING_RE = '(?!x)x' ESCAPE_SEQUENCE_RE = re.compile(r''' ( \\U........ # 8-digit hex escapes | \\u.... # 4-digit hex escapes | \\x.. # 2-digit hex escapes | \\[0-7]{1,3} # Octal escapes | \\N\{[^}]+\} # Unicode characters by name | \\[\\'"abfnrtv] # Single-character escapes )''', re.UNICODE | re.VERBOSE) def decode_escapes(s): def decode_match(match): return codecs.decode(match.group(0), 'unicode-escape') return ESCAPE_SEQUENCE_RE.sub(decode_match, s) # noinspection PyPep8Naming class Lexer(object): t_ignore = ' \t\r\n' literals = '()],}' keywords = { 'true': 'TRUE', 'false': 'FALSE', 'null': 'NULL' } keyword_to_val = { 'TRUE': True, 'FALSE': False, 'NULL': None } def __init__(self, yaql_operators): self._operators_table = yaql_operators.operators self.tokens = [ 'KEYWORD_STRING', 'QUOTED_STRING', 'NUMBER', 'FUNC', 'DOLLAR', 'INDEXER', 'MAPPING', 'MAP' ] + list(self.keywords.values()) for op_symbol, op_record in six.iteritems(self._operators_table): if op_symbol in ('[]', '{}'): continue lexem_name = op_record[2] setattr(self, 't_' + lexem_name, re.escape(op_symbol)) self.tokens.append(lexem_name) self.t_MAPPING = re.escape(yaql_operators.name_value_op) \ if yaql_operators.name_value_op else NEVER_MATCHING_RE self.t_INDEXER = '\\[' \ if '[]' in self._operators_table else NEVER_MATCHING_RE self.t_MAP = '{' \ if '{}' in self._operators_table else NEVER_MATCHING_RE @staticmethod def t_DOLLAR(t): """ \\$\\w* """ return t @staticmethod def t_NUMBER(t): """ \\b\\d+(\\.?\\d+)?\\b """ if '.' in t.value: t.value = float(t.value) else: t.value = int(t.value) return t @staticmethod def t_FUNC(t): """ \\b[^\\W\\d]\\w*\\( """ val = t.value[:-1] t.value = val return t def t_KEYWORD_STRING(self, t): """ (?!__)\\b[^\\W\\d]\\w*\\b """ if t.value in self._operators_table: t.type = self._operators_table[t.value][2] else: t.type = self.keywords.get(t.value, 'KEYWORD_STRING') t.value = self.keyword_to_val.get(t.type, t.value) return t @staticmethod def t_QUOTED_STRING(t): """ '([^'\\\\]|\\\\.)*' """ t.value = decode_escapes(t.value[1:-1]) return t @staticmethod def t_DOUBLE_QUOTED_STRING(t): """ "([^"\\\\]|\\\\.)*" """ t.value = decode_escapes(t.value[1:-1]) t.type = 'QUOTED_STRING' return t @staticmethod def t_QUOTED_VERBATIM_STRING(t): """ `([^`\\\\]|\\\\.)*` """ t.value = t.value[1:-1].replace('\\`', '`') t.type = 'QUOTED_STRING' return t @staticmethod def t_error(t): raise exceptions.YaqlLexicalException(t.value[0], t.lexpos) yaql-1.1.3/yaql/language/parser.py000066400000000000000000000154151305545650400170760ustar00rootroot00000000000000# 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 six from yaql.language import exceptions from yaql.language import expressions from yaql.language import utils class Parser(object): def __init__(self, lexer, yaql_operators, engine): self.tokens = lexer.tokens self._aliases = {} self._generate_operator_funcs(yaql_operators, engine) def _generate_operator_funcs(self, yaql_operators, engine): binary_doc = '' unary_doc = '' precedence_dict = {} for up, bp, op_name, op_alias in yaql_operators.operators.values(): self._aliases[op_name] = op_alias if up: l = precedence_dict.setdefault( (abs(up), 'l' if up > 0 else 'r'), []) l.append('UNARY_' + op_name if bp else op_name) unary_doc += ('value : ' if not unary_doc else '\n| ') spec_prefix = '{0} value' if up > 0 else 'value {0}' if bp: unary_doc += (spec_prefix + ' %prec UNARY_{0}').format( op_name) else: unary_doc += spec_prefix.format(op_name) if bp: l = precedence_dict.setdefault( (abs(bp), 'l' if bp > 0 else 'r'), []) if op_name == 'INDEXER': l.extend(('LIST', 'INDEXER')) elif op_name == 'MAP': l.append('MAP') else: l.append(op_name) binary_doc += (( 'value : ' if not binary_doc else '\n| ') + 'value {0} value'.format(op_name)) # noinspection PyProtectedMember def p_binary(this, p): alias = this._aliases.get(p.slice[2].type) p[0] = expressions.BinaryOperator(p[2], p[1], p[3], alias) # noinspection PyProtectedMember def p_unary(this, p): if p[1] in yaql_operators.operators: alias = this._aliases.get(p.slice[1].type) p[0] = expressions.UnaryOperator(p[1], p[2], alias) else: alias = this._aliases.get(p.slice[2].type) p[0] = expressions.UnaryOperator(p[2], p[1], alias) p_binary.__doc__ = binary_doc self.p_binary = six.create_bound_method(p_binary, self) p_unary.__doc__ = unary_doc self.p_unary = six.create_bound_method(p_unary, self) precedence = [] for i in range(1, len(precedence_dict) + 1): for oa in ('r', 'l'): value = precedence_dict.get((i, oa)) if value: precedence.append( (('left',) if oa is 'l' else ('right',)) + tuple(value) ) precedence.insert(0, ('left', ',')) precedence.reverse() self.precedence = tuple(precedence) def p_value_call(this, p): """ func : value '(' args ')' """ arg = () if len(p) > 4: arg = p[3] p[0] = expressions.Function('#call', p[1], *arg) if engine.allow_delegates: self.p_value_call = six.create_bound_method(p_value_call, self) @staticmethod def p_value_to_const(p): """ value : QUOTED_STRING | NUMBER | TRUE | FALSE | NULL """ p[0] = expressions.Constant(p[1]) @staticmethod def p_keyword_constant(p): """ value : KEYWORD_STRING """ p[0] = expressions.KeywordConstant(p[1]) @staticmethod def p_value_to_dollar(p): """ value : DOLLAR """ p[0] = expressions.GetContextValue(expressions.Constant(p[1])) @staticmethod def p_val_in_parenthesis(p): """ value : '(' value ')' """ p[0] = expressions.Wrap(p[2]) @staticmethod def p_args(p): """ args : arglist | named_arglist | arglist ',' named_arglist | incomplete_arglist ',' named_arglist | """ if len(p) == 1: p[0] = [] elif len(p) == 2: p[0] = p[1] else: p[0] = p[1] + p[3] @staticmethod def p_indexer(p): """ value : value INDEXER args ']' """ p[0] = expressions.IndexExpression(p[1], *p[3]) @staticmethod def p_list(p): """ value : INDEXER args ']' %prec LIST """ p[0] = expressions.ListExpression(*p[2]) @staticmethod def p_map(p): """ value : MAP args '}' """ p[0] = expressions.MapExpression(*p[2]) @staticmethod def p_val_to_function(p): """ value : func """ p[0] = p[1] @staticmethod def p_named_arg_definition(p): """ named_arg : value MAPPING value """ p[0] = expressions.MappingRuleExpression(p[1], p[3]) @staticmethod def p_arg_list(p): """ arglist : value | ',' arglist | arglist ',' arglist | incomplete_arglist ',' arglist """ if len(p) == 2: p[0] = [p[1]] elif len(p) == 3: p[0] = [utils.NO_VALUE] + p[2] elif len(p) == 4: p[0] = p[1] + p[3] @staticmethod def p_incomplete_arg_list(p): """ incomplete_arglist : arglist ',' """ p[0] = p[1] + [utils.NO_VALUE] @staticmethod def p_named_arg_list(p): """ named_arglist : named_arg | named_arglist ',' named_arg """ if len(p) == 2: p[0] = [p[1]] else: p[0] = p[1] + [p[3]] @staticmethod def p_function(p): """ func : FUNC args ')' """ arg = () if len(p) > 3: arg = p[2] p[0] = expressions.Function(p[1], *arg) @staticmethod def p_error(p): if p: raise exceptions.YaqlGrammarException( p.lexer.lexdata, p.value, p.lexpos) else: raise exceptions.YaqlGrammarException(None, None, None) yaql-1.1.3/yaql/language/runner.py000066400000000000000000000145071305545650400171140ustar00rootroot00000000000000# 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 sys import six from yaql.language import exceptions from yaql.language import expressions from yaql.language import utils from yaql.language import yaqltypes def call(name, context, args, kwargs, engine, receiver=utils.NO_VALUE, data_context=None, use_convention=False, function_filter=None): if data_context is None: data_context = context if function_filter is None: function_filter = lambda fd, ctx: True if receiver is utils.NO_VALUE: predicate = lambda fd, ctx: fd.is_function and function_filter(fd, ctx) else: predicate = lambda fd, ctx: fd.is_method and function_filter(fd, ctx) all_overloads = context.collect_functions( name, predicate, use_convention=use_convention) if not all_overloads: if receiver is utils.NO_VALUE: raise exceptions.NoFunctionRegisteredException(name) else: raise exceptions.NoMethodRegisteredException(name, receiver) else: delegate = choose_overload( name, all_overloads, engine, receiver, data_context, args, kwargs) try: result = delegate() utils.limit_memory_usage(engine, (1, result)) return result except StopIteration as e: six.reraise( exceptions.WrappedException, exceptions.WrappedException(e), sys.exc_info()[2]) def choose_overload(name, candidates, engine, receiver, context, args, kwargs): def raise_ambiguous(): if receiver is utils.NO_VALUE: raise exceptions.AmbiguousFunctionException(name) else: raise exceptions.AmbiguousMethodException(name, receiver) def raise_not_found(): if receiver is utils.NO_VALUE: raise exceptions.NoMatchingFunctionException(name) else: raise exceptions.NoMatchingMethodException(name, receiver) candidates2 = [] lazy_params = None no_kwargs = None if receiver is not utils.NO_VALUE: args = (receiver,) + args for level in candidates: new_level = [] for c in level: if no_kwargs is None: no_kwargs = c.no_kwargs args, kwargs = translate_args(no_kwargs, args, kwargs) elif no_kwargs != c.no_kwargs: raise_ambiguous() mapping = c.map_args(args, kwargs, context, engine) if mapping is None: continue pos, kwd = mapping lazy = set() for i, pos_arg in enumerate(pos): if isinstance(pos_arg.value_type, yaqltypes.LazyParameterType): lazy.add(i) for key, value in six.iteritems(kwd): if isinstance(value.value_type, yaqltypes.LazyParameterType): lazy.add(key) if lazy_params is None: lazy_params = lazy elif lazy_params != lazy: raise_ambiguous() new_level.append((c, mapping)) if new_level: candidates2.append(new_level) if len(candidates2) == 0: raise_not_found() arg_evaluator = lambda i, arg: ( arg(utils.NO_VALUE, context, engine) if (i not in lazy_params and isinstance(arg, expressions.Expression) and not isinstance(arg, expressions.Constant)) else arg ) args = tuple(arg_evaluator(i, arg) for i, arg in enumerate(args)) for key, value in six.iteritems(kwargs): kwargs[key] = arg_evaluator(key, value) delegate = None winner_mapping = None for level in candidates2: for c, mapping in level: try: d = c.get_delegate(receiver, engine, context, args, kwargs) except exceptions.ArgumentException: pass else: if delegate is not None: if _is_specialization_of(winner_mapping, mapping): continue elif not _is_specialization_of(mapping, winner_mapping): raise_ambiguous() delegate = d winner_mapping = mapping if delegate is not None: break if delegate is None: raise_not_found() return lambda: delegate() def translate_args(without_kwargs, args, kwargs): if without_kwargs: if len(kwargs) > 0: raise exceptions.ArgumentException(six.next(iter(kwargs))) return args, {} pos_args = [] kw_args = {} for t in args: if isinstance(t, expressions.MappingRuleExpression): param_name = t.source if isinstance(param_name, expressions.KeywordConstant): param_name = param_name.value else: raise exceptions.MappingTranslationException() kw_args[param_name] = t.destination else: pos_args.append(t) for key, value in six.iteritems(kwargs): if key in kw_args: raise exceptions.MappingTranslationException() else: kw_args[key] = value return tuple(pos_args), kw_args def _is_specialization_of(mapping1, mapping2): args_mapping1, kwargs_mapping1 = mapping1 args_mapping2, kwargs_mapping2 = mapping2 res = False for a1, a2 in six.moves.zip(args_mapping1, args_mapping2): if a2.value_type.is_specialization_of(a1.value_type): return False elif a1.value_type.is_specialization_of(a2.value_type): res = True for key, a1 in six.iteritems(kwargs_mapping1): a2 = kwargs_mapping2[key] if a2.value_type.is_specialization_of(a1.value_type): return False elif a1.value_type.is_specialization_of(a2.value_type): res = True return res yaql-1.1.3/yaql/language/specs.py000066400000000000000000000464651305545650400167300ustar00rootroot00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import inspect import six from yaql.language import exceptions from yaql.language import utils from yaql.language import yaqltypes NO_DEFAULT = utils.create_marker('') class ParameterDefinition(object): __slots__ = ('value_type', 'name', 'position', 'default', 'alias') def __init__(self, name, value_type=None, position=None, alias=None, default=None): self.value_type = value_type self.name = name self.position = position self.default = default self.alias = alias def __repr__(self): return '{0} => position={1} value_type={2} default={3}'.format( self.name, self.position, self.value_type, self.default) def clone(self): return ParameterDefinition(self.name, self.value_type, self.position, self.alias, self.default) class FunctionDefinition(object): __slots__ = ('is_method', 'is_function', 'name', 'parameters', 'payload', 'doc', 'no_kwargs', 'meta') def __init__(self, name, payload, parameters=None, doc='', meta=None, is_function=True, is_method=False, no_kwargs=False): self.is_method = is_method self.is_function = is_function self.name = name self.parameters = {} if not parameters else parameters self.payload = payload self.doc = doc self.no_kwargs = no_kwargs self.meta = meta or {} def __call__(self, engine, context, receiver=utils.NO_VALUE): def func(*args, **kwargs): if receiver is not utils.NO_VALUE: args = (receiver,) + args return self.get_delegate(receiver, engine, context, args, kwargs)() return func def clone(self): parameters = dict( (key, p.clone()) for key, p in six.iteritems(self.parameters)) res = FunctionDefinition( self.name, self.payload, parameters, self.doc, self.meta, self.is_function, self.is_method, self.no_kwargs) return res def strip_hidden_parameters(self): fd = self.clone() keys_to_remove = set() for k, v in six.iteritems(fd.parameters): if not isinstance(v.value_type, yaqltypes.HiddenParameterType): continue keys_to_remove.add(k) if v.position is not None: for v2 in six.itervalues(fd.parameters): if v2.position is not None and v2.position > v.position: v2.position -= 1 for key in keys_to_remove: del fd.parameters[key] return fd def set_parameter(self, name, value_type=None, nullable=None, alias=None, overwrite=False): if isinstance(name, ParameterDefinition): if name.name in self.parameters and not overwrite: raise exceptions.DuplicateParameterDecoratorException( function_name=self.name or self.payload.__name__, param_name=name.name) self.parameters[name.name] = name return name if six.PY2: spec = inspect.getargspec(self.payload) if isinstance(name, int): if 0 <= name < len(spec.args): name = spec.args[name] elif name == len(spec.args) and spec.varargs is not None: name = spec.varargs else: raise IndexError('argument position is out of range') arg_name = name if name == spec.keywords: position = None arg_name = '**' elif name == spec.varargs: position = len(spec.args) arg_name = '*' elif name not in spec.args: raise exceptions.NoParameterFoundException( function_name=self.name or self.payload.__name__, param_name=name) else: position = spec.args.index(name) default = NO_DEFAULT if spec.defaults is not None and name in spec.args: index = spec.args.index(name) - len(spec.args) if index >= -len(spec.defaults): default = spec.defaults[index] else: spec = inspect.getfullargspec(self.payload) if isinstance(name, int): if 0 <= name < len(spec.args): name = spec.args[name] elif name == len(spec.args) and spec.varargs is not None: name = spec.varargs else: raise IndexError('argument position is out of range') arg_name = name if name == spec.varkw: position = None arg_name = '**' elif name == spec.varargs: position = len(spec.args) arg_name = '*' elif name in spec.kwonlyargs: position = None elif name not in spec.args: raise exceptions.NoParameterFoundException( function_name=self.name or self.payload.__name__, param_name=name) else: position = spec.args.index(name) default = NO_DEFAULT if spec.defaults is not None and name in spec.args: index = spec.args.index(name) - len(spec.args) if index >= -len(spec.defaults): default = spec.defaults[index] elif spec.kwonlydefaults is not None: default = spec.kwonlydefaults.get(name, NO_DEFAULT) if arg_name in self.parameters and not overwrite: raise exceptions.DuplicateParameterDecoratorException( function_name=self.name or self.payload.__name__, param_name=name) yaql_type = value_type p_nullable = nullable if value_type is None: if p_nullable is None: p_nullable = True base_type = object \ if default in (None, NO_DEFAULT, utils.NO_VALUE) \ else type(default) yaql_type = yaqltypes.PythonType(base_type, p_nullable) elif not isinstance(value_type, yaqltypes.SmartType): if p_nullable is None: p_nullable = default is None yaql_type = yaqltypes.PythonType(value_type, p_nullable) pd = ParameterDefinition( name, yaql_type, position, alias, default ) self.parameters[arg_name] = pd return pd def insert_parameter(self, name, value_type=None, nullable=None, alias=None, overwrite=False): pd = self.set_parameter(name, value_type, nullable, alias, overwrite) for p in six.itervalues(self.parameters): if p is pd: continue if p.position is not None and p.position >= pd.position: p.position += 1 def map_args(self, args, kwargs, context, engine): kwargs = dict(kwargs) positional_args = len(args) * [ self.parameters.get('*', utils.NO_VALUE)] max_dst_positional_args = len(args) + len(self.parameters) positional_fix_table = max_dst_positional_args * [0] keyword_args = {} for p in six.itervalues(self.parameters): if p.position is not None and isinstance( p.value_type, yaqltypes.HiddenParameterType): for index in range(p.position + 1, len(positional_fix_table)): positional_fix_table[index] += 1 for key, p in six.iteritems(self.parameters): arg_name = p.alias or p.name if p.position is not None and key != '*': arg_position = p.position - positional_fix_table[p.position] if isinstance(p.value_type, yaqltypes.HiddenParameterType): continue elif arg_position < len(args) and args[arg_position] \ is not utils.NO_VALUE: if arg_name in kwargs: return None positional_args[arg_position] = p elif arg_name in kwargs: keyword_args[arg_name] = p del kwargs[arg_name] elif p.default is NO_DEFAULT: return None elif arg_position < len(args) and args[arg_position]: positional_args[arg_position] = p elif p.position is None and key != '**': if isinstance(p.value_type, yaqltypes.HiddenParameterType): continue elif arg_name in kwargs: keyword_args[arg_name] = p del kwargs[arg_name] elif p.default is NO_DEFAULT: return None if len(kwargs) > 0: if '**' in self.parameters: argdef = self.parameters['**'] for key in six.iterkeys(kwargs): keyword_args[key] = argdef else: return None for i in range(len(positional_args)): if positional_args[i] is utils.NO_VALUE: return None value = args[i] if value is utils.NO_VALUE: value = positional_args[i].default if not positional_args[i].value_type.check(value, context, engine): return None for kwd in six.iterkeys(kwargs): if not keyword_args[kwd].value_type.check( kwargs[kwd], context, engine): return None return tuple(positional_args), keyword_args def get_delegate(self, receiver, engine, context, args, kwargs): def checked(val, param): if not param.value_type.check(val, context, engine): raise exceptions.ArgumentException(param.name) def convert_arg_func(context2): try: return param.value_type.convert( val, receiver, context2, self, engine) except exceptions.ArgumentValueException: raise exceptions.ArgumentException(param.name) return convert_arg_func kwargs = kwargs.copy() kwargs = dict(kwargs) positional = 0 for arg_name, p in six.iteritems(self.parameters): if p.position is not None and arg_name != '*': positional += 1 positional_args = positional * [None] positional_fix_table = positional * [0] keyword_args = {} for p in six.itervalues(self.parameters): if p.position is not None and isinstance( p.value_type, yaqltypes.HiddenParameterType): for index in range(p.position + 1, positional): positional_fix_table[index] += 1 for key, p in six.iteritems(self.parameters): arg_name = p.alias or p.name if p.position is not None and key != '*': if isinstance(p.value_type, yaqltypes.HiddenParameterType): positional_args[p.position] = checked(None, p) positional -= 1 elif p.position - positional_fix_table[p.position] < len( args) and args[p.position - positional_fix_table[ p.position]] is not utils.NO_VALUE: if arg_name in kwargs: raise exceptions.ArgumentException(p.name) positional_args[p.position] = checked( args[p.position - positional_fix_table[ p.position]], p) elif arg_name in kwargs: positional_args[p.position] = checked( kwargs.pop(arg_name), p) elif p.default is not NO_DEFAULT: positional_args[p.position] = checked(p.default, p) else: raise exceptions.ArgumentException(p.name) elif p.position is None and key != '**': if isinstance(p.value_type, yaqltypes.HiddenParameterType): keyword_args[key] = checked(None, p) elif arg_name in kwargs: keyword_args[key] = checked(kwargs.pop(arg_name), p) elif p.default is not NO_DEFAULT: keyword_args[key] = checked(p.default, p) else: raise exceptions.ArgumentException(p.name) if len(args) > positional: if '*' in self.parameters: argdef = self.parameters['*'] positional_args.extend( map(lambda t: checked(t, argdef), args[positional:])) else: raise exceptions.ArgumentException('*') if len(kwargs) > 0: if '**' in self.parameters: argdef = self.parameters['**'] for key, value in six.iteritems(kwargs): keyword_args[key] = checked(value, argdef) else: raise exceptions.ArgumentException('**') def func(): new_context = context.create_child_context() result = self.payload( *tuple(map(lambda t: t(new_context), positional_args)), **dict(map(lambda t: (t[0], t[1](new_context)), six.iteritems(keyword_args))) ) return result return func def is_valid_method(self): min_position = len(self.parameters) min_arg = None for p in six.itervalues(self.parameters): if p.position is not None and p.position < min_position and \ not isinstance(p.value_type, yaqltypes.HiddenParameterType): min_position = p.position min_arg = p return min_arg and not isinstance( min_arg.value_type, yaqltypes.LazyParameterType) def _get_function_definition(func): if not hasattr(func, '__yaql_function__'): fd = FunctionDefinition(None, func, {}, func.__doc__) func.__yaql_function__ = fd return func.__yaql_function__ def _infer_parameter_type(name): if name == 'context' or name == '__context': return yaqltypes.Context() elif name == 'engine' or name == '__engine': return yaqltypes.Engine() elif name == 'yaql_interface' or name == '__yaql_interface': return yaqltypes.YaqlInterface() def convert_function_name(function_name, convention): if not function_name: return function_name function_name = function_name.rstrip('_') if not convention: return function_name if not function_name[0].isalpha(): finish = function_name.find(function_name[0], 1) if finish <= 1: return function_name return function_name[:finish + 1] + convention.convert_function_name( function_name[finish + 1:]) return convention.convert_function_name(function_name) def convert_parameter_name(parameter_name, convention): if not parameter_name: return parameter_name parameter_name = parameter_name.rstrip('_') if not convention: return parameter_name return convention.convert_parameter_name(parameter_name) def get_function_definition(func, name=None, function=None, method=None, convention=None, parameter_type_func=None): if parameter_type_func is None: parameter_type_func = _infer_parameter_type fd = _get_function_definition(func).clone() if six.PY2: spec = inspect.getargspec(func) for arg in spec.args: if arg not in fd.parameters: fd.set_parameter(arg, parameter_type_func(arg)) if spec.varargs and '*' not in fd.parameters: fd.set_parameter(spec.varargs, parameter_type_func(spec.varargs)) if spec.keywords and '**' not in fd.parameters: fd.set_parameter(spec.keywords, parameter_type_func(spec.keywords)) else: spec = inspect.getfullargspec(func) for arg in spec.args + spec.kwonlyargs: if arg not in fd.parameters: fd.set_parameter(arg, parameter_type_func(arg)) if spec.varargs and '*' not in fd.parameters: fd.set_parameter(spec.varargs, parameter_type_func(spec.varargs)) if spec.varkw and '**' not in fd.parameters: fd.set_parameter(spec.varkw, parameter_type_func(spec.varkw)) if name is not None: fd.name = name elif fd.name is None: fd.name = convert_function_name(fd.payload.__name__, convention) elif convention is not None: fd.name = convert_function_name(fd.name, convention) if function is not None: fd.is_function = function if method is not None: fd.is_method = method if convention: for p in six.itervalues(fd.parameters): if p.alias is None: p.alias = convert_parameter_name(p.name, convention) return fd def _parameter(name, value_type=None, nullable=None, alias=None): def wrapper(func): fd = _get_function_definition(func) fd.set_parameter(name, value_type, nullable, alias) return func return wrapper def parameter(name, value_type=None, nullable=None, alias=None): if value_type is not None and isinstance( value_type, yaqltypes.HiddenParameterType): raise ValueError('Use inject() for hidden parameters') return _parameter(name, value_type, nullable=nullable, alias=alias) def inject(name, value_type=None, nullable=None, alias=None): if value_type is not None and not isinstance( value_type, yaqltypes.HiddenParameterType): raise ValueError('Use parameter() for normal function parameters') return _parameter(name, value_type, nullable=nullable, alias=alias) def name(function_name): def wrapper(func): fd = _get_function_definition(func) fd.name = function_name return func return wrapper def method(func): fd = _get_function_definition(func) fd.is_method = True fd.is_function = False return func def extension_method(func): fd = _get_function_definition(func) fd.is_method = True fd.is_function = True return func def no_kwargs(func): fd = _get_function_definition(func) fd.no_kwargs = True return func def meta(name, value): def wrapper(func): fd = _get_function_definition(func) fd.meta[name] = value return func return wrapper def yaql_property(source_type): def decorator(func): @name('#property#{0}'.format(get_function_definition(func).name)) @parameter('obj', source_type) def wrapper(obj): return func(obj) return wrapper return decorator yaql-1.1.3/yaql/language/utils.py000066400000000000000000000157301305545650400167420ustar00rootroot00000000000000# 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 collections import re import sys import six from yaql.language import exceptions from yaql.language import lexer def create_marker(msg): class MarkerClass(object): def __repr__(self): return msg return MarkerClass() KEYWORD_REGEX = re.compile(lexer.Lexer.t_KEYWORD_STRING.__doc__.strip()) NO_VALUE = create_marker('') def is_iterator(obj): return isinstance(obj, collections.Iterator) def is_iterable(obj): return isinstance(obj, collections.Iterable) and not isinstance( obj, six.string_types + (MappingType,)) def is_sequence(obj): return isinstance(obj, collections.Sequence) and not isinstance( obj, six.string_types) def is_mutable(obj): return isinstance(obj, (collections.MutableSequence, collections.MutableSet, collections.MutableMapping)) SequenceType = collections.Sequence MutableSequenceType = collections.MutableSequence SetType = collections.Set MutableSetType = collections.MutableSet MappingType = collections.Mapping MutableMappingType = collections.MutableMapping IterableType = collections.Iterable IteratorType = collections.Iterator QueueType = collections.deque def convert_input_data(obj, rec=None): if rec is None: rec = convert_input_data if isinstance(obj, six.string_types): return obj if isinstance(obj, six.text_type) else six.text_type(obj) elif isinstance(obj, SequenceType): return tuple(rec(t, rec) for t in obj) elif isinstance(obj, MappingType): return FrozenDict((rec(key, rec), rec(value, rec)) for key, value in six.iteritems(obj)) elif isinstance(obj, MutableSetType): return frozenset(rec(t, rec) for t in obj) elif isinstance(obj, IterableType): return six.moves.map(lambda v: rec(v, rec), obj) else: return obj def convert_output_data(obj, limit_func, engine, rec=None): if rec is None: rec = convert_output_data if isinstance(obj, collections.Mapping): result = {} for key, value in limit_func(six.iteritems(obj)): result[rec(key, limit_func, engine, rec)] = rec( value, limit_func, engine, rec) return result elif isinstance(obj, SetType): set_type = list if convert_sets_to_lists(engine) else set return set_type(rec(t, limit_func, engine, rec) for t in limit_func(obj)) elif isinstance(obj, (tuple, list)): seq_type = list if convert_tuples_to_lists(engine) else type(obj) return seq_type(rec(t, limit_func, engine, rec) for t in limit_func(obj)) elif is_iterable(obj): return list(rec(t, limit_func, engine, rec) for t in limit_func(obj)) else: return obj def convert_sets_to_lists(engine): return engine.options.get('yaql.convertSetsToLists', False) def convert_tuples_to_lists(engine): return engine.options.get('yaql.convertTuplesToLists', True) class MappingRule(object): def __init__(self, source, destination): self.source = source self.destination = destination class FrozenDict(collections.Mapping): def __init__(self, *args, **kwargs): self._d = dict(*args, **kwargs) self._hash = None def __iter__(self): return iter(self._d) def __len__(self): return len(self._d) def __getitem__(self, key): return self._d[key] def get(self, key, default=None): return self._d.get(key, default) def __hash__(self): if self._hash is None: self._hash = 0 for pair in six.iteritems(self): self._hash ^= hash(pair) return self._hash def __repr__(self): return repr(self._d) def memorize(collection, engine): if not is_iterator(collection): return collection yielded = [] class RememberingIterator(six.Iterator): def __init__(self): self.seq = iter(collection) self.index = 0 def __iter__(self): return RememberingIterator() def __next__(self): if self.index < len(yielded): self.index += 1 return yielded[self.index - 1] else: val = next(self.seq) yielded.append(val) limit_memory_usage(engine, (1, yielded)) self.index += 1 return val return RememberingIterator() def get_max_collection_size(engine): return engine.options.get('yaql.limitIterators', -1) def get_memory_quota(engine): return engine.options.get('yaql.memoryQuota', -1) def limit_iterable(iterable, limit_or_engine): if isinstance(limit_or_engine, int): max_count = limit_or_engine else: max_count = get_max_collection_size(limit_or_engine) if isinstance(iterable, (SequenceType, MappingType, SetType)): if 0 <= max_count < len(iterable): raise exceptions.CollectionTooLargeException(max_count) return iterable def limiting_iterator(): for i, t in enumerate(iterable): if 0 <= max_count <= i: raise exceptions.CollectionTooLargeException(max_count) yield t return limiting_iterator() def limit_memory_usage(quota_or_engine, *args): if isinstance(quota_or_engine, int): quota = quota_or_engine else: quota = get_memory_quota(quota_or_engine) if quota <= 0: return total = 0 for t in args: total += t[0] * sys.getsizeof(t[1], 0) if total > quota: raise exceptions.MemoryQuotaExceededException() def to_extension_method(name, context): layers = context.collect_functions( name, lambda t, ctx: not t.is_function or not t.is_method, use_convention=True) if len(layers) > 1: raise ValueError( 'Multi layer functions are not supported by this helper method') if len(layers) > 0: for spec in layers[0]: spec = spec.clone() spec.is_function = True spec.is_method = True yield spec def is_keyword(text): return KEYWORD_REGEX.match(text) is not None def filter_parameters_dict(parameters): parameters = dict(parameters) for name in parameters.keys(): if not is_keyword(name): del parameters[name] return parameters yaql-1.1.3/yaql/language/yaqltypes.py000066400000000000000000000524301305545650400176330ustar00rootroot00000000000000# 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 collections import datetime from dateutil import tz import six from yaql.language import exceptions from yaql.language import expressions from yaql.language import utils from yaql import yaql_interface @six.add_metaclass(abc.ABCMeta) class HiddenParameterType(object): # noinspection PyMethodMayBeStatic,PyUnusedLocal def check(self, value, context, engine, *args, **kwargs): return True @six.add_metaclass(abc.ABCMeta) class LazyParameterType(object): pass @six.add_metaclass(abc.ABCMeta) class SmartType(object): __slots__ = ('nullable',) def __init__(self, nullable): self.nullable = nullable def check(self, value, context, engine, *args, **kwargs): if value is None and not self.nullable: return False return True def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): if not self.check(value, context, engine, *args, **kwargs): raise exceptions.ArgumentValueException() utils.limit_memory_usage(engine, (1, value)) return value def is_specialization_of(self, other): return False class GenericType(SmartType): __slots__ = ('checker', 'converter') def __init__(self, nullable, checker=None, converter=None): super(GenericType, self).__init__(nullable) self.checker = checker self.converter = converter def check(self, value, context, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value if not super(GenericType, self).check( value, context, engine, *args, **kwargs): return False if value is None or isinstance(value, expressions.Expression): return True if not self.checker: return True return self.checker(value, context, *args, **kwargs) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value super(GenericType, self).convert( value, receiver, context, function_spec, engine, *args, **kwargs) if value is None or not self.converter: return value return self.converter(value, receiver, context, function_spec, engine, *args, **kwargs) class PythonType(GenericType): __slots__ = ('python_type', 'validators') def __init__(self, python_type, nullable=True, validators=None): self.python_type = python_type if not validators: validators = [lambda _: True] if not isinstance(validators, (list, tuple)): validators = [validators] self.validators = validators super(PythonType, self).__init__( nullable, lambda value, context, *args, **kwargs: isinstance( value, self.python_type) and all( map(lambda t: t(value), self.validators))) def is_specialization_of(self, other): if not isinstance(other, PythonType): return False try: len(self.python_type) len(other.python_type) except Exception: return ( issubclass(self.python_type, other.python_type) and not issubclass(other.python_type, self.python_type) ) else: return False class MappingRule(LazyParameterType, SmartType): __slots__ = tuple() def __init__(self): super(MappingRule, self).__init__(False) def check(self, value, context, *args, **kwargs): return isinstance(value, expressions.MappingRuleExpression) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): super(MappingRule, self).convert( value, receiver, context, function_spec, engine, *args, **kwargs) wrap = lambda func: lambda: func(receiver, context, engine) return utils.MappingRule(wrap(value.source), wrap(value.destination)) class String(PythonType): __slots__ = tuple() def __init__(self, nullable=False): super(String, self).__init__(six.string_types, nullable=nullable) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): value = super(String, self).convert( value, receiver, context, function_spec, engine, *args, **kwargs) return None if value is None else six.text_type(value) class Integer(PythonType): __slots__ = tuple() def __init__(self, nullable=False): super(Integer, self).__init__( six.integer_types, nullable=nullable, validators=[lambda t: not isinstance(t, bool)]) class DateTime(PythonType): __slots__ = tuple() utctz = tz.tzutc() def __init__(self, nullable=False): super(DateTime, self).__init__(datetime.datetime, nullable=nullable) def convert(self, value, *args, **kwargs): if isinstance(value, datetime.datetime): if value.tzinfo is None: return value.replace(tzinfo=self.utctz) else: return value return super(DateTime, self).convert(value, *args, **kwargs) class Iterable(PythonType): __slots__ = tuple() def __init__(self, validators=None, nullable=False): super(Iterable, self).__init__( collections.Iterable, nullable, [ lambda t: not isinstance(t, six.string_types + ( utils.MappingType,))] + (validators or [])) def check(self, value, context, engine, *args, **kwargs): if isinstance(value, utils.MappingType) and engine.options.get( 'yaql.iterableDicts', False): return True return super(Iterable, self).check( value, context, engine, *args, **kwargs) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): res = super(Iterable, self).convert( value, receiver, context, function_spec, engine, *args, **kwargs) return None if res is None else utils.limit_iterable(res, engine) class Iterator(Iterable): __slots__ = tuple() def __init__(self, validators=None, nullable=False): super(Iterator, self).__init__( validators=[utils.is_iterator] + (validators or []), nullable=nullable) class Sequence(PythonType): __slots__ = tuple() def __init__(self, validators=None, nullable=False): super(Sequence, self).__init__( collections.Sequence, nullable, [ lambda t: not isinstance(t, six.string_types + (dict,))] + ( validators or [])) class Number(PythonType): __slots__ = tuple() def __init__(self, nullable=False): super(Number, self).__init__( six.integer_types + (float,), nullable, validators=[lambda t: not isinstance(t, bool)]) class Lambda(LazyParameterType, SmartType): __slots__ = tuple() def __init__(self, with_context=False, method=False): super(Lambda, self).__init__(True) self.with_context = with_context self.method = method def check(self, value, context, *args, **kwargs): if self.method and isinstance( value, expressions.Expression) and not value.uses_receiver: return False return super(Lambda, self).check(value, context, *args, **kwargs) @staticmethod def _publish_params(context, args, kwargs): for i, param in enumerate(args): context['$' + str(i + 1)] = param for arg_name, arg_value in kwargs.items(): context['$' + arg_name] = arg_value def _call(self, value, receiver, context, engine, args, kwargs): self._publish_params(context, args, kwargs) if isinstance(value, expressions.Expression): result = value(receiver, context, engine) elif six.callable(value): result = value(*args, **kwargs) else: result = value return result def convert(self, value, receiver, context, function_spec, engine, *convert_args, **convert_kwargs): super(Lambda, self).convert( value, receiver, context, function_spec, engine, *convert_args, **convert_kwargs) if value is None: return None elif six.callable(value) and hasattr(value, '__unwrapped__'): value = value.__unwrapped__ def func(*args, **kwargs): if self.method and self.with_context: new_receiver, new_context = args[:2] args = args[2:] elif self.method and not self.with_context: new_receiver, new_context = \ args[0], context.create_child_context() args = args[1:] elif not self.method and self.with_context: new_receiver, new_context = utils.NO_VALUE, args[0] args = args[1:] else: new_receiver, new_context = \ utils.NO_VALUE, context.create_child_context() return self._call(value, new_receiver, new_context, engine, args, kwargs) func.__unwrapped__ = value return func class Super(HiddenParameterType, SmartType): __slots__ = ('with_context', 'method', 'with_name') def __init__(self, with_context=False, method=None, with_name=False): self.with_context = with_context self.method = method self.with_name = with_name super(Super, self).__init__(False) @staticmethod def _find_function_context(spec, context): while context is not None: if spec in context: return context context = context.parent raise exceptions.NoFunctionRegisteredException( spec.name) def convert(self, value, receiver, context, function_spec, engine, *convert_args, **convert_kwargs): if six.callable(value) and hasattr(value, '__unwrapped__'): value = value.__unwrapped__ def func(*args, **kwargs): function_context = self._find_function_context( function_spec, context) parent_function_context = function_context.parent if parent_function_context is None: raise exceptions.NoFunctionRegisteredException( function_spec.name) new_name = function_spec.name if self.with_name: new_name = args[0] args = args[1:] new_receiver = receiver if self.method is True: new_receiver = args[0] args = args[1:] elif self.method is False: new_receiver = utils.NO_VALUE if self.with_context: new_context = args[0] args = args[1:] else: new_context = context.create_child_context() return parent_function_context( new_name, engine, new_receiver, new_context)(*args, **kwargs) func.__unwrapped__ = value return func class Context(HiddenParameterType, SmartType): __slots__ = tuple() def __init__(self): super(Context, self).__init__(False) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): return context class Delegate(HiddenParameterType, SmartType): __slots__ = ('name', 'with_context', 'method', 'use_convention') def __init__(self, name=None, with_context=False, method=False, use_convention=True): super(Delegate, self).__init__(False) self.name = name self.with_context = with_context self.method = method self.use_convention = use_convention def convert(self, value, receiver, context, function_spec, engine, *convert_args, **convert_kwargs): if six.callable(value) and hasattr(value, '__unwrapped__'): value = value.__unwrapped__ def func(*args, **kwargs): name = self.name if not name: name = args[0] args = args[1:] new_receiver = utils.NO_VALUE if self.method: new_receiver = args[0] args = args[1:] if self.with_context: new_context = args[0] args = args[1:] else: new_context = context.create_child_context() return new_context( name, engine, new_receiver, use_convention=self.use_convention)(*args, **kwargs) func.__unwrapped__ = value return func class Receiver(HiddenParameterType, SmartType): __slots__ = tuple() def __init__(self): super(Receiver, self).__init__(False) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): return receiver class Engine(HiddenParameterType, SmartType): __slots__ = tuple() def __init__(self): super(Engine, self).__init__(False) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): return engine class FunctionDefinition(HiddenParameterType, SmartType): __slots__ = tuple() def __init__(self): super(FunctionDefinition, self).__init__(False) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): return function_spec class Constant(SmartType): __slots__ = ('expand',) def __init__(self, nullable, expand=True): self.expand = expand super(Constant, self).__init__(nullable) def check(self, value, context, *args, **kwargs): return super(Constant, self).check( value, context, *args, **kwargs) and ( value is None or isinstance(value, expressions.Constant)) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): super(Constant, self).convert( value, receiver, context, function_spec, engine, *args, **kwargs) return value.value if self.expand else value class YaqlExpression(LazyParameterType, SmartType): __slots__ = ('_expression_type',) def __init__(self, expression_type=None): super(YaqlExpression, self).__init__(False) if expression_type and not utils.is_sequence(expression_type): expression_type = (expression_type,) self._expression_types = expression_type def check(self, value, context, *args, **kwargs): if not self._expression_types: return isinstance(value, expressions.Expression) return any(t == type(value) for t in self._expression_types) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): super(YaqlExpression, self).convert( value, receiver, context, function_spec, engine, *args, **kwargs) return value class StringConstant(Constant): __slots__ = tuple() def __init__(self, nullable=False): super(StringConstant, self).__init__(nullable) def check(self, value, context, *args, **kwargs): return super(StringConstant, self).check( value, context, *args, **kwargs) and ( value is None or isinstance(value.value, six.string_types)) class Keyword(Constant): __slots__ = tuple() def __init__(self, expand=True): super(Keyword, self).__init__(False, expand) def check(self, value, context, *args, **kwargs): return isinstance(value, expressions.KeywordConstant) class BooleanConstant(Constant): __slots__ = tuple() def __init__(self, nullable=False, expand=True): super(BooleanConstant, self).__init__(nullable, expand) def check(self, value, context, *args, **kwargs): return super(BooleanConstant, self).check( value, context, *args, **kwargs) and ( value is None or type(value.value) is bool) class NumericConstant(Constant): __slots__ = tuple() def __init__(self, nullable=False, expand=True): super(NumericConstant, self).__init__(nullable, expand) def check(self, value, context, *args, **kwargs): return super(NumericConstant, self).check( value, context, *args, **kwargs) and ( value is None or isinstance( value.value, six.integer_types + (float,)) and type(value.value) is not bool) @six.add_metaclass(abc.ABCMeta) class SmartTypeAggregation(SmartType): __slots__ = ('types',) def __init__(self, *args, **kwargs): self.nullable = kwargs.pop('nullable', False) super(SmartTypeAggregation, self).__init__(self.nullable) self.types = [] for item in args: if isinstance(item, (type, tuple)): item = PythonType(item) if isinstance(item, (HiddenParameterType, LazyParameterType)): raise ValueError('Special smart types are not supported') self.types.append(item) class AnyOf(SmartTypeAggregation): __slots__ = tuple() def _check_match(self, value, context, engine, *args, **kwargs): for type_to_check in self.types: check_result = type_to_check.check( value, context, engine, *args, **kwargs) if check_result: return type_to_check def check(self, value, context, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value if value is None: return self.nullable check_result = self._check_match( value, context, engine, *args, **kwargs) return True if check_result else False def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value if value is None: if self.nullable: return None else: suitable_type = None else: suitable_type = self._check_match( value, context, engine, *args, **kwargs) if suitable_type: return suitable_type.convert( value, receiver, context, function_spec, engine, *args, **kwargs) raise exceptions.ArgumentValueException() class Chain(SmartTypeAggregation): __slots__ = tuple() def _check_match(self, value, context, engine, *args, **kwargs): for type_to_check in self.types: check_result = type_to_check.check( value, context, engine, *args, **kwargs) if check_result: return type_to_check def check(self, value, context, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value if value is None: return self.nullable for type_to_check in self.types: if not type_to_check.check( value, context, engine, *args, **kwargs): return False return True def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value if value is None: if self.nullable: return None raise exceptions.ArgumentValueException() for smart_type in self.types: value = smart_type.convert( value, receiver, context, function_spec, engine, *args, **kwargs) return value class NotOfType(SmartType): __slots__ = ('smart_type',) def __init__(self, smart_type, nullable=True): if isinstance(smart_type, (type, tuple)): smart_type = PythonType(smart_type, nullable=nullable) self.smart_type = smart_type super(NotOfType, self).__init__(nullable) def check(self, value, context, engine, *args, **kwargs): if isinstance(value, expressions.Constant): value = value.value if not super(NotOfType, self).check( value, context, engine, *args, **kwargs): return False if value is None or isinstance(value, expressions.Expression): return True return not self.smart_type.check( value, context, engine, *args, **kwargs) class YaqlInterface(HiddenParameterType, SmartType): __slots__ = tuple() def __init__(self): super(YaqlInterface, self).__init__(False) def convert(self, value, receiver, context, function_spec, engine, *args, **kwargs): return yaql_interface.YaqlInterface(context, engine, receiver) yaql-1.1.3/yaql/legacy.py000066400000000000000000000030061305545650400152540ustar00rootroot00000000000000# 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 yaql from yaql.language import factory from yaql.standard_library import legacy as std_legacy class YaqlFactory(factory.YaqlFactory): def __init__(self, allow_delegates=False): # noinspection PyTypeChecker super(YaqlFactory, self).__init__( keyword_operator=None, allow_delegates=allow_delegates) self.insert_operator( 'or', True, '=>', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True) def create(self, options=None): options = dict(options or {}) options['yaql.convertTuplesToLists'] = False options['yaql.iterableDicts'] = True return super(YaqlFactory, self).create(options) def create_context(*args, **kwargs): tuples = kwargs.pop('tuples', True) context = yaql.create_context(*args, **kwargs) context = context.create_child_context() std_legacy.register(context, tuples) return context yaql-1.1.3/yaql/standard_library/000077500000000000000000000000001305545650400167635ustar00rootroot00000000000000yaql-1.1.3/yaql/standard_library/__init__.py000066400000000000000000000000001305545650400210620ustar00rootroot00000000000000yaql-1.1.3/yaql/standard_library/boolean.py000066400000000000000000000071341305545650400207610ustar00rootroot00000000000000# 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. """ Whenever an expression is used in the context of boolean operations, the following values are interpreted as false: ``false``, ``null``, numeric zero of any type, empty strings, empty dict, empty list, empty set, zero timespan. All other values are interpreted as true. """ from yaql.language import specs from yaql.language import yaqltypes @specs.parameter('left', yaqltypes.Lambda()) @specs.parameter('right', yaqltypes.Lambda()) @specs.name('#operator_and') def and_(left, right): """:yaql:operator and Returns left operand if it evaluates to false. Otherwise evaluates right operand and returns it. :signature: left and right :arg left: left operand :argType left: any :arg right: right operand :argType right: any :returnType: any (left or right operand types) .. code:: yaql> 1 and 0 0 yaql> 1 and 2 2 yaql> [] and 1 [] """ return left() and right() @specs.parameter('left', yaqltypes.Lambda()) @specs.parameter('right', yaqltypes.Lambda()) @specs.name('#operator_or') def or_(left, right): """:yaql:operator or Returns left operand if it evaluates to true. Otherwise evaluates right operand and returns it. :signature: left or right :arg left: left operand :argType left: any :arg right: right operand :argType right: any :returnType: any (left or right operand types) .. code:: yaql> 1 or 0 1 yaql> 1 or 2 1 yaql> [] or 1 1 """ return left() or right() @specs.name('#unary_operator_not') def not_(arg): """:yaql:operator not Returns true if arg evaluates to false. Otherwise returns false. :signature: not arg :arg arg: value to be converted :argType arg: any :returnType: boolean .. code:: yaql> not true false yaql> not {} true yaql> not [1] false """ return not arg def bool_(value): """:yaql:bool Returns true or false after value type conversion to boolean. Function returns false if value is 0, false, empty list, empty dictionary, empty string, empty set, and timespan(). All other values are considered to be true. :signature: bool(value) :arg value: value to be converted :argType value: any :returnType: boolean .. code:: yaql> bool(1) true yaql> bool([]) false """ return bool(value) def is_boolean(value): """:yaql:isBoolean Returns true if value is boolean, otherwise false. :signature: isBoolean(value) :arg value: value to check :argType value: any :returnType: boolean .. code:: yaql> isBoolean(false) true yaql> isBoolean(0) false """ return isinstance(value, bool) def register(context): context.register_function(and_) context.register_function(or_) context.register_function(not_) context.register_function(bool_) context.register_function(is_boolean) yaql-1.1.3/yaql/standard_library/branching.py000066400000000000000000000124721305545650400212760ustar00rootroot00000000000000# 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. """ The module describes branching functions such as switch, case, and others. """ from yaql.language import specs from yaql.language import yaqltypes @specs.parameter('args', yaqltypes.MappingRule()) @specs.no_kwargs def switch(*args): """:yaql:switch Returns the value of the first argument for which the key evaluates to true, null if there is no such arg. :signature: switch([args]) :arg [args]: mappings with keys to check for true and appropriate values :argType [args]: chain of mapping :returnType: any (types of values of args) .. code:: yaql> switch("ab" > "abc" => 1, "ab" >= "abc" => 2, "ab" < "abc" => 3) 3 """ for mapping in args: if mapping.source(): return mapping.destination() @specs.parameter('args', yaqltypes.Lambda()) def select_case(*args): """:yaql:selectCase Returns a zero-based index of the first predicate evaluated to true. If there is no such predicate, returns the count of arguments. All the predicates after the first one which was evaluated to true remain unevaluated. :signature: selectCase([args]) :arg [args]: predicates to check for true :argType [args]: chain of predicates :returnType: integer .. code:: yaql> selectCase("ab" > "abc", "ab" >= "abc", "ab" < "abc") 2 """ index = 0 for f in args: if f(): return index index += 1 return index @specs.parameter('args', yaqltypes.Lambda()) def select_all_cases(*args): """:yaql:selectAllCases Evaluates input predicates and returns an iterator to collection of zero-based indexes of predicates which were evaluated to true. The actual evaluation is done lazily as the iterator advances, not during the function call. :signature: selectAllCases([args]) :arg [args]: predicates to check for true :argType [args]: chain of predicates :returnType: iterator .. code:: yaql> selectAllCases("ab" > "abc", "ab" <= "abc", "ab" < "abc") [1, 2] """ for i, f in enumerate(args): if f(): yield i @specs.parameter('args', yaqltypes.Lambda()) def examine(*args): """:yaql:examine Evaluates predicates one by one and casts the evaluation results to boolean. Returns an iterator to collection of casted results. The actual evaluation is done lazily as the iterator advances, not during the function call. :signature: examine([args]) :arg [args]: predicates to be evaluated :argType [args]: chain of predicates functions :returnType: iterator .. code:: yaql> examine("ab" > "abc", "ab" <= "abc", "ab" < "abc") [false, true, true] """ for f in args: yield bool(f()) @specs.parameter('case', int) @specs.parameter('args', yaqltypes.Lambda()) @specs.method def switch_case(case, *args): """:yaql:switchCase Returns evaluated `case`-th argument. If case is less than 0 or greater than the amount of predicates, returns evaluated last argument. Returns null if no args are provided. :signature: case.switchCase([args]) :recieverArg case: index of predicate to be evaluated :argType case: integer :arg [args]: input predicates :argType [args]: chain of any types :returnType: any .. code:: yaql> 1.switchCase('a', 1 + 1, []) 2 yaql> 2.switchCase('a', 1 + 1, []) [] yaql> 3.switchCase('a', 1 + 1, []) [] yaql> let(1) -> selectCase($ < 0, $ = 0).switchCase("less than 0", "equal to 0", "greater than 0") "greater than 0" """ if 0 <= case < len(args): return args[case]() if len(args) == 0: return None return args[-1]() @specs.parameter('args', yaqltypes.Lambda()) def coalesce(*args): """:yaql:coalesce Returns the first predicate which evaluates to non-null value. Returns null if no arguments are provided or if all of them are null. :signature: coalesce([args]) :arg [args]: input arguments :argType [args]: chain of any types :returnType: any .. code:: yaql> coalesce(null) null yaql> coalesce(null, [1, 2, 3][0], "abc") 1 yaql> coalesce(null, false, 1) false """ for f in args: res = f() if res is not None: return res return None def register(context): context.register_function(switch) context.register_function(select_case) context.register_function(switch_case) context.register_function(select_all_cases) context.register_function(examine) context.register_function(coalesce) yaql-1.1.3/yaql/standard_library/collections.py000066400000000000000000001037411305545650400216610ustar00rootroot00000000000000# 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. """ Functions that produce or consume finite collections - lists, dicts and sets. """ import itertools import six from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes import yaql.standard_library.queries @specs.parameter('args', nullable=True) @specs.inject('delegate', yaqltypes.Delegate('to_list', method=True)) def list_(delegate, *args): """:yaql:list Returns list of provided args and unpacks arg element if it's iterable. :signature: list([args]) :arg [args]: arguments to create a list :argType [args]: chain of any types :returnType: list .. code:: yaql> list(1, "", range(2)) [1, "", 0, 1] """ def rec(seq): for t in seq: if utils.is_iterator(t): for t2 in rec(t): yield t2 else: yield t return delegate(rec(args)) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def flatten(collection): """:yaql:flatten Returns an iterator to the recursive traversal of collection. :signature: collection.flatten() :receiverArg collection: collection to be traversed :argType collection: iterable :returnType: list .. code:: yaql> ["a", ["b", [2,3]]].flatten() ["a", "b", 2, 3] """ for t in collection: if utils.is_iterable(t): for t2 in flatten(t): yield t2 else: yield t @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def to_list(collection): """:yaql:toList Returns list built from iterable. :signature: collection.toList() :receiverArg collection: collection to be transferred to list :argType collection: iterable :returnType: list .. code:: yaql> range(3).toList() [0, 1, 2] """ if isinstance(collection, tuple): return collection return tuple(collection) @specs.parameter('args', nullable=True) @specs.name('#list') def build_list(engine, *args): """:yaql:list Returns list of provided args. :signature: list([args]) :arg [args]: arguments to create a list :argType [args]: any types :returnType: list .. code:: yaql> list(1, "", 2) [1, "", 2] """ utils.limit_memory_usage(engine, *((1, t) for t in args)) return tuple(args) @specs.no_kwargs @specs.parameter('args', utils.MappingRule) def dict_(engine, *args): """:yaql:dict Returns dictionary of provided keyword values. :signature: dict([args]) :arg [args]: arguments to create a dictionary :argType [args]: mappings :returnType: dictionary .. code:: yaql> dict(a => 1, b => 2) { "a": 1, "b": 2} """ utils.limit_memory_usage(engine, *((1, arg) for arg in args)) return utils.FrozenDict((t.source, t.destination) for t in args) @specs.parameter('items', yaqltypes.Iterable()) @specs.no_kwargs def dict__(items, engine): """:yaql:dict Returns dictionary with keys and values built on items pairs. :signature: dict(items) :arg items: list of pairs [key, value] for building dictionary :argType items: list :returnType: dictionary .. code:: yaql> dict([["a", 2], ["b", 4]]) {"a": 2, "b": 4} """ result = {} for t in items: it = iter(t) key = next(it) value = next(it) result[key] = value utils.limit_memory_usage(engine, (1, result)) return utils.FrozenDict(result) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('key_selector', yaqltypes.Lambda()) @specs.parameter('value_selector', yaqltypes.Lambda()) @specs.method def to_dict(collection, engine, key_selector, value_selector=None): """:yaql:dict Returns dict built on collection where keys are keySelector applied to collection elements and values are valueSelector applied to collection elements. :signature: collection.toDict(keySelector, valueSelector => null) :receiverArg collection: collection to build dict from :argType collection: iterable :arg keySelector: lambda function to get keys from collection elements :argType keySelector: lambda :arg valueSelector: lambda function to get values from collection elements. null by default, which means values to be collection items :argType valueSelector: lambda :returnType: dictionary .. code:: yaql> [1, 2].toDict($, $ + 1) {"1": 2, "2": 3} """ result = {} for t in collection: key = key_selector(t) value = t if value_selector is None else value_selector(t) result[key] = value utils.limit_memory_usage(engine, (1, result)) return result @specs.parameter('d', utils.MappingType, alias='dict') @specs.parameter('key', yaqltypes.Keyword()) @specs.name('#operator_.') def dict_keyword_access(d, key): """:yaql:operator . Returns value of a dictionary by given key. :signature: left.right :arg left: input dictionary :argType left: dictionary :arg right: key :argType right: keyword :returnType: any (appropriate value type) .. code:: yaql> {"a" => 1, "b" => 2}.a 1 """ return d[key] @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('#indexer') def dict_indexer(d, key): """:yaql:operator indexer Returns value of a dictionary by given key. :signature: left[right] :arg left: input dictionary :argType left: dictionary :arg right: key :argType right: keyword :returnType: any (appropriate value type) .. code:: yaql> {"a" => 1, "b" => 2}["a"] 1 """ return d[key] @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('#indexer') def dict_indexer_with_default(d, key, default): """:yaql:operator indexer Returns value of a dictionary by given key or default if there is no such key. :signature: left[right, default] :arg left: input dictionary :argType left: dictionary :arg right: key :argType right: keyword :arg default: default value to be returned if key is missing in dictionary :argType default: any :returnType: any (appropriate value type) .. code:: yaql> {"a" => 1, "b" => 2}["c", 3] 3 """ return d.get(key, default) @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('get') @specs.method def dict_get(d, key, default=None): """:yaql:get Returns value of a dictionary by given key or default if there is no such key. :signature: dict.get(key, default => null) :receiverArg dict: input dictionary :argType dict: dictionary :arg key: key :argType key: keyword :arg default: default value to be returned if key is missing in dictionary. null by default :argType default: any :returnType: any (appropriate value type) .. code:: yaql> {"a" => 1, "b" => 2}.get("c") null yaql> {"a" => 1, "b" => 2}.get("c", 3) 3 """ return d.get(key, default) @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('set') @specs.method @specs.no_kwargs def dict_set(engine, d, key, value): """:yaql:set Returns dict with provided key set to value. :signature: dict.set(key, value) :receiverArg dict: input dictionary :argType dict: dictionary :arg key: key :argType key: keyword :arg value: value to be set to input key :argType value: any :returnType: dictionary .. code:: yaql> {"a" => 1, "b" => 2}.set("c", 3) {"a": 1, "b": 2, "c": 3} yaql> {"a" => 1, "b" => 2}.set("b", 3) {"a": 1, "b": 3} """ utils.limit_memory_usage(engine, (1, d), (1, key), (1, value)) return utils.FrozenDict(itertools.chain(six.iteritems(d), ((key, value),))) @specs.parameter('d', utils.MappingType, alias='dict') @specs.parameter('replacements', utils.MappingType) @specs.name('set') @specs.method @specs.no_kwargs def dict_set_many(engine, d, replacements): """:yaql:set Returns dict with replacements keys set to replacements values. :signature: dict.set(replacements) :receiverArg dict: input dictionary :argType dict: dictionary :arg replacements: mapping with keys and values to be set on input dict :argType key: dictionary :returnType: dictionary .. code:: yaql> {"a" => 1, "b" => 2}.set({"b" => 3, "c" => 4}) {"a": 1, "c": 4, "b": 3} """ utils.limit_memory_usage(engine, (1, d), (1, replacements)) return utils.FrozenDict(itertools.chain( six.iteritems(d), six.iteritems(replacements))) @specs.no_kwargs @specs.method @specs.parameter('args', utils.MappingRule) @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('set') def dict_set_many_inline(engine, d, *args): """:yaql:set Returns dict with args keys set to args values. :signature: dict.set([args]) :receiverArg dict: input dictionary :argType dict: dictionary :arg [args]: key-values to be set on input dict :argType [args]: chain of mappings :returnType: dictionary .. code:: yaql> {"a" => 1, "b" => 2}.set("b" => 3, "c" => 4) {"a": 1, "c": 4, "b": 3} """ utils.limit_memory_usage(engine, (1, d), *((1, arg) for arg in args)) return utils.FrozenDict(itertools.chain( six.iteritems(d), ((t.source, t.destination) for t in args))) @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('keys') @specs.method def dict_keys(d): """:yaql:keys Returns an iterator over the dictionary keys. :signature: dict.keys() :receiverArg dict: input dictionary :argType dict: dictionary :returnType: iterator .. code:: yaql> {"a" => 1, "b" => 2}.keys() ["a", "b"] """ return six.iterkeys(d) @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('values') @specs.method def dict_values(d): """:yaql:values Returns an iterator over the dictionary values. :signature: dict.values() :receiverArg dict: input dictionary :argType dict: dictionary :returnType: iterator .. code:: yaql> {"a" => 1, "b" => 2}.values() [1, 2] """ return six.itervalues(d) @specs.parameter('d', utils.MappingType, alias='dict') @specs.name('items') @specs.method def dict_items(d): """:yaql:items Returns an iterator over pairs [key, value] of input dict. :signature: dict.items() :receiverArg dict: input dictionary :argType dict: dictionary :returnType: iterator .. code:: yaql> {"a" => 1, "b" => 2}.items() [["a", 1], ["b", 2]] """ return six.iteritems(d) @specs.parameter('lst', yaqltypes.Sequence(), alias='list') @specs.parameter('index', int, nullable=False) @specs.name('#indexer') def list_indexer(lst, index): """:yaql:operator indexer Returns value of sequence by given index. :signature: left[right] :arg left: input sequence :argType left: sequence :arg right: index :argType right: integer :returnType: any (appropriate value type) .. code:: yaql> ["a", "b"][0] "a" """ return lst[index] @specs.parameter('value', nullable=True) @specs.parameter('collection', yaqltypes.Iterable()) @specs.name('#operator_in') def in_(value, collection): """:yaql:operator in Returns true if there is at least one occurrence of value in collection, false otherwise. :signature: left in right :arg left: value to be checked for occurrence :argType left: any :arg right: collection to find occurrence in :argType right: iterable :returnType: boolean .. code:: yaql> "a" in ["a", "b"] true """ return value in collection @specs.parameter('value', nullable=True) @specs.parameter('collection', yaqltypes.Iterable()) @specs.method def contains(collection, value): """:yaql:contains Returns true if value is contained in collection, false otherwise. :signature: collection.contains(value) :receiverArg collection: collection to find occurrence in :argType collection: iterable :arg value: value to be checked for occurrence :argType value: any :returnType: boolean .. code:: yaql> ["a", "b"].contains("a") true """ return value in collection @specs.parameter('key', nullable=True) @specs.parameter('d', utils.MappingType, alias='dict') @specs.method def contains_key(d, key): """:yaql:containsKey Returns true if the dictionary contains the key, false otherwise. :signature: dict.containsKey(key) :receiverArg dict: dictionary to find occurrence in :argType dict: mapping :arg key: value to be checked for occurrence :argType key: any :returnType: boolean .. code:: yaql> {"a" => 1, "b" => 2}.containsKey("a") true """ return key in d @specs.parameter('value', nullable=True) @specs.parameter('d', utils.MappingType, alias='dict') @specs.method def contains_value(d, value): """:yaql:containsValue Returns true if the dictionary contains the value, false otherwise. :signature: dict.containsValue(value) :receiverArg dict: dictionary to find occurrence in :argType dict: mapping :arg value: value to be checked for occurrence :argType value: any :returnType: boolean .. code:: yaql> {"a" => 1, "b" => 2}.containsValue("a") false yaql> {"a" => 1, "b" => 2}.containsValue(2) true """ return value in six.itervalues(d) @specs.parameter('left', yaqltypes.Iterable()) @specs.parameter('right', yaqltypes.Iterable()) @specs.name('#operator_+') def combine_lists(left, right, engine): """:yaql:operator + Returns two iterables concatenated. :signature: left + right :arg left: left list :argType left: iterable :arg right: right list :argType right: iterable :returnType: iterable .. code:: yaql> [1, 2] + [3] [1, 2, 3] """ if isinstance(left, tuple) and isinstance(right, tuple): utils.limit_memory_usage(engine, (1, left), (1, right)) return left + right elif isinstance(left, frozenset) and isinstance(right, frozenset): utils.limit_memory_usage(engine, (1, left), (1, right)) return left.union(right) return yaql.standard_library.queries.concat(left, right) @specs.parameter('left', yaqltypes.Sequence()) @specs.parameter('right', int) @specs.name('#operator_*') def list_by_int(left, right, engine): """:yaql:operator * Returns sequence repeated count times. :signature: left * right :arg left: input sequence :argType left: sequence :arg right: multiplier :argType right: integer :returnType: sequence .. code:: yaql> [1, 2] * 2 [1, 2, 1, 2] """ utils.limit_memory_usage(engine, (-right + 1, []), (right, left)) return left * right @specs.parameter('left', int) @specs.parameter('right', yaqltypes.Sequence()) @specs.name('#operator_*') def int_by_list(left, right, engine): """:yaql:operator * Returns sequence repeated count times. :signature: left * right :arg left: multiplier :argType left: integer :arg right: input sequence :argType right: sequence :returnType: sequence .. code:: yaql> 2 * [1, 2] [1, 2, 1, 2] """ return list_by_int(right, left, engine) @specs.parameter('left', utils.MappingType) @specs.parameter('right', utils.MappingType) @specs.name('#operator_+') def combine_dicts(left, right, engine): """:yaql:operator + Returns combined left and right dictionaries. :signature: left + right :arg left: left dictionary :argType left: mapping :arg right: right dictionary :argType right: mapping :returnType: mapping .. code:: yaql> {"a" => 1, b => 2} + {"b" => 3, "c" => 4} {"a": 1, "c": 4, "b": 3} """ utils.limit_memory_usage(engine, (1, left), (1, right)) d = dict(left) d.update(right) return utils.FrozenDict(d) def is_list(arg): """:yaql:isList Returns true if arg is a list, false otherwise. :signature: isList(arg) :arg arg: value to be checked :argType arg: any :returnType: boolean .. code:: yaql> isList([1, 2]) true yaql> isList({"a" => 1}) false """ return utils.is_sequence(arg) def is_dict(arg): """:yaql:isDict Returns true if arg is dictionary, false otherwise. :signature: isDict(arg) :arg arg: value to be checked :argType arg: any :returnType: boolean .. code:: yaql> isDict([1, 2]) false yaql> isDict({"a" => 1}) true """ return isinstance(arg, utils.MappingType) def is_set(arg): """:yaql:isSet Returns true if arg is set, false otherwise. :signature: isSet(arg) :arg arg: value to be checked :argType arg: any :returnType: boolean .. code:: yaql> isSet({"a" => 1}) false yaql> isSet(set(1, 2)) true """ return isinstance(arg, utils.SetType) @specs.parameter('d', utils.MappingType, alias='dict') @specs.extension_method @specs.name('len') def dict_len(d): """:yaql:len Returns size of the dictionary. :signature: dict.len() :receiverArg dict: input dictionary :argType dict: mapping :returnType: integer .. code:: yaql> {"a" => 1, "b" => 2}.len() 2 """ return len(d) @specs.parameter('sequence', yaqltypes.Sequence()) @specs.extension_method @specs.name('len') def sequence_len(sequence): """:yaql:len Returns length of the list. :signature: sequence.len() :receiverArg sequence: input sequence :argType dict: sequence :returnType: integer .. code:: yaql> [0, 1, 2].len() 3 """ return len(sequence) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('position', int) @specs.parameter('count', int) def delete(collection, position, count=1): """:yaql:delete Returns collection with removed [position, position+count) elements. :signature: collection.delete(position, count => 1) :receiverArg collection: input collection :argType collection: iterable :arg position: index to start remove :argType position: integer :arg count: how many elements to remove, 1 by default :argType position: integer :returnType: iterable .. code:: yaql> [0, 1, 3, 4, 2].delete(2, 2) [0, 1, 2] """ for i, t in enumerate(collection): if count >= 0 and not position <= i < position + count: yield t elif count < 0 and not i >= position: yield t @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('position', int) @specs.parameter('count', int) def replace(collection, position, value, count=1): """:yaql:replace Returns collection where [position, position+count) elements are replaced with value. :signature: collection.replace(position, value, count => 1) :receiverArg collection: input collection :argType collection: iterable :arg position: index to start replace :argType position: integer :arg value: value to be replaced with :argType value: any :arg count: how many elements to replace, 1 by default :argType count: integer :returnType: iterable .. code:: yaql> [0, 1, 3, 4, 2].replace(2, 100, 2) [0, 1, 100, 2] """ yielded = False for i, t in enumerate(collection): if (count >= 0 and position <= i < position + count or count < 0 and i >= position): if not yielded: yielded = True yield value else: yield t @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('position', int) @specs.parameter('count', int) @specs.parameter('values', yaqltypes.Iterable()) def replace_many(collection, position, values, count=1): """:yaql:replaceMany Returns collection where [position, position+count) elements are replaced with values items. :signature: collection.replaceMany(position, values, count => 1) :receiverArg collection: input collection :argType collection: iterable :arg position: index to start replace :argType position: integer :arg values: items to replace :argType values: iterable :arg count: how many elements to replace, 1 by default :argType count: integer :returnType: iterable .. code:: yaql> [0, 1, 3, 4, 2].replaceMany(2, [100, 200], 2) [0, 1, 100, 200, 2] """ yielded = False for i, t in enumerate(collection): if (count >= 0 and position <= i < position + count or count < 0 and i >= position): if not yielded: for v in values: yield v yielded = True else: yield t @specs.method @specs.name('delete') @specs.parameter('d', utils.MappingType, alias='dict') def delete_keys(d, *keys): """:yaql:delete Returns dict with keys removed. :signature: dict.delete([args]) :receiverArg dict: input dictionary :argType dict: mapping :arg [args]: keys to be removed from dictionary :argType [args]: chain of keywords :returnType: mapping .. code:: yaql> {"a" => 1, "b" => 2, "c" => 3}.delete("a", "c") {"b": 2} """ return delete_keys_seq(d, keys) @specs.method @specs.name('deleteAll') @specs.parameter('d', utils.MappingType, alias='dict') @specs.parameter('keys', yaqltypes.Iterable()) def delete_keys_seq(d, keys): """:yaql:deleteAll Returns dict with keys removed. Keys are provided as an iterable collection. :signature: dict.deleteAll(keys) :receiverArg dict: input dictionary :argType dict: mapping :arg keys: keys to be removed from dictionary :argType keys: iterable :returnType: mapping .. code:: yaql> {"a" => 1, "b" => 2, "c" => 3}.deleteAll(["a", "c"]) {"b": 2} """ copy = dict(d) for t in keys: copy.pop(t, None) return copy @specs.method @specs.parameter('collection', yaqltypes.Iterable(validators=[ lambda x: not isinstance(x, utils.SetType)] )) @specs.parameter('value', nullable=True) @specs.parameter('position', int) @specs.name('insert') def iter_insert(collection, position, value): """:yaql:insert Returns collection with inserted value at the given position. :signature: collection.insert(position, value) :receiverArg collection: input collection :argType collection: iterable :arg position: index for insertion. value is inserted in the end if position greater than collection size :argType position: integer :arg value: value to be inserted :argType value: any :returnType: iterable .. code:: yaql> [0, 1, 3].insert(2, 2) [0, 1, 2, 3] """ i = -1 for i, t in enumerate(collection): if i == position: yield value yield t if position > i: yield value @specs.method @specs.parameter('collection', yaqltypes.Sequence()) @specs.parameter('value', nullable=True) @specs.parameter('position', int) @specs.name('insert') def list_insert(collection, position, value): """:yaql:insert Returns collection with inserted value at the given position. :signature: collection.insert(position, value) :receiverArg collection: input collection :argType collection: sequence :arg position: index for insertion. value is inserted in the end if position greater than collection size :argType position: integer :arg value: value to be inserted :argType value: any :returnType: sequence .. code:: yaql> [0, 1, 3].insert(2, 2) [0, 1, 2, 3] """ copy = list(collection) copy.insert(position, value) return copy @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('values', yaqltypes.Iterable()) @specs.parameter('position', int) def insert_many(collection, position, values): """:yaql:insertMany Returns collection with inserted values at the given position. :signature: collection.insertMany(position, values) :receiverArg collection: input collection :argType collection: iterable :arg position: index for insertion. value is inserted in the end if position greater than collection size :argType position: integer :arg values: items to be inserted :argType values: iterable :returnType: iterable .. code:: yaql> [0, 1, 3].insertMany(2, [2, 22]) [0, 1, 2, 22, 3] """ i = -1 if position < 0: for j in values: yield j for i, t in enumerate(collection): if i == position: for j in values: yield j yield t if position > i: for j in values: yield j @specs.parameter('s', utils.SetType, alias='set') @specs.extension_method @specs.name('len') def set_len(s): """:yaql:len Returns size of the set. :signature: set.len() :receiverArg set: input set :argType set: set :returnType: integer .. code:: yaql> set(0, 1, 2).len() 3 """ return len(s) @specs.parameter('args', nullable=True) @specs.inject('delegate', yaqltypes.Delegate('to_set', method=True)) def set_(delegate, *args): """:yaql:set Returns set initialized with args. :signature: set([args]) :arg [args]: args to build a set :argType [args]: chain of any type :returnType: set .. code:: yaql> set(0, "", [1, 2]) [0, "", [1, 2]] """ def rec(seq): for t in seq: if utils.is_iterator(t): for t2 in rec(t): yield t2 else: yield t return delegate(rec(args)) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def to_set(collection): """:yaql:toSet Returns set built from iterable. :signature: collection.toSet() :receiverArg collection: collection to build a set :argType collection: iterable :returnType: set .. code:: yaql> [0, 1, 1, 2].toSet() [0, 1, 2] """ return frozenset(collection) @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.method def union(left, right): """:yaql:union Returns union of two sets. :signature: left.union(right) :receiverArg left: input set :argType left: set :arg right: input set :argType right: set :returnType: set .. code:: yaql> set(0, 1).union(set(1, 2)) [0, 1, 2] """ return left.union(right) @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.name('#operator_<') def set_lt(left, right): """:yaql:operator < Returns true if left set is subset of right set and left size is strictly less than right size, false otherwise. :signature: left < right :arg left: left set :argType left: set :arg right: right set :argType right: set :returnType: boolean .. code:: yaql> set(0) < set(0, 1) true """ return left < right @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.name('#operator_<=') def set_lte(left, right): """:yaql:operator <= Returns true if left set is subset of right set. :signature: left <= right :arg left: left set :argType left: set :arg right: right set :argType right: set :returnType: boolean .. code:: yaql> set(0, 1) <= set(0, 1) true """ return left <= right @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.name('#operator_>=') def set_gte(left, right): """:yaql:operator >= Returns true if right set is subset of left set. :signature: left >= right :arg left: left set :argType left: set :arg right: right set :argType right: set :returnType: boolean .. code:: yaql> set(0, 1) >= set(0, 1) true """ return left >= right @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.name('#operator_>') def set_gt(left, right): """:yaql:operator > Returns true if right set is subset of left set and left size is strictly greater than right size, false otherwise. :signature: left > right :arg left: left set :argType left: set :arg right: right set :argType right: set :returnType: boolean .. code:: yaql> set(0, 1) > set(0, 1) false """ return left > right @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.method def intersect(left, right): """:yaql:intersect Returns set with elements common to left and right sets. :signature: left.intersect(right) :receiverArg left: left set :argType left: set :arg right: right set :argType right: set :returnType: set .. code:: yaql> set(0, 1, 2).intersect(set(0, 1)) [0, 1] """ return left.intersection(right) @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.method def difference(left, right): """:yaql:difference Return the difference of left and right sets as a new set. :signature: left.difference(right) :receiverArg left: left set :argType left: set :arg right: right set :argType right: set :returnType: set .. code:: yaql> set(0, 1, 2).difference(set(0, 1)) [2] """ return left.difference(right) @specs.parameter('left', utils.SetType) @specs.parameter('right', utils.SetType) @specs.method def symmetric_difference(left, right): """:yaql:symmetricDifference Returns symmetric difference of left and right sets as a new set. :signature: left.symmetricDifference(right) :receiverArg left: left set :argType left: set :arg right: right set :argType right: set :returnType: set .. code:: yaql> set(0, 1, 2).symmetricDifference(set(0, 1, 3)) [2, 3] """ return left.symmetric_difference(right) @specs.parameter('s', utils.SetType, alias='set') @specs.method @specs.name('add') def set_add(s, *values): """:yaql:add Returns a new set with added args. :signature: set.add([args]) :receiverArg set: input set :argType set: set :arg [args]: values to be added to set :argType [args]: chain of any type :returnType: set .. code:: yaql> set(0, 1).add("", [1, 2, 3]) [0, 1, "", [1, 2, 3]] """ return s.union(frozenset(values)) @specs.parameter('s', utils.SetType, alias='set') @specs.method @specs.name('remove') def set_remove(s, *values): """:yaql:remove Returns the set with excluded values provided in arguments. :signature: set.remove([args]) :receiverArg set: input set :argType set: set :arg [args]: values to be removed from set :argType [args]: chain of any type :returnType: set .. code:: yaql> set(0, 1, "", [1, 2, 3]).remove("", 0, [1, 2, 3]) [1] """ return s.difference(frozenset(values)) def register(context, no_sets=False): context.register_function(list_) context.register_function(build_list) context.register_function(to_list) context.register_function(flatten) context.register_function(list_indexer) context.register_function(dict_) context.register_function(dict_, name='#map') context.register_function(dict__) context.register_function(to_dict) context.register_function(dict_keyword_access) context.register_function(dict_indexer) context.register_function(dict_indexer_with_default) context.register_function(dict_get) context.register_function(dict_set) context.register_function(dict_set_many) context.register_function(dict_set_many_inline) context.register_function(dict_keys) context.register_function(dict_values) context.register_function(dict_items) context.register_function(in_) context.register_function(contains_key) context.register_function(contains_value) context.register_function(combine_lists) context.register_function(list_by_int) context.register_function(int_by_list) context.register_function(combine_dicts) context.register_function(is_dict) context.register_function(is_list) context.register_function(dict_len) context.register_function(sequence_len) context.register_function(delete) context.register_function(delete_keys) context.register_function(delete_keys_seq) context.register_function(iter_insert) context.register_function(list_insert) context.register_function(replace) context.register_function(replace_many) context.register_function(insert_many) context.register_function(contains) if not no_sets: context.register_function(is_set) context.register_function(set_) context.register_function(to_set) context.register_function(set_len) context.register_function(set_lt) context.register_function(set_lte) context.register_function(set_gt) context.register_function(set_gte) context.register_function(set_add) context.register_function(set_remove) context.register_function(union) context.register_function(intersect) context.register_function(difference) context.register_function( difference, name='#operator_-', function=True, method=False) context.register_function(symmetric_difference) yaql-1.1.3/yaql/standard_library/common.py000066400000000000000000000175521305545650400206370ustar00rootroot00000000000000# 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. """ Common module describes comparison operators for different types. Comparing with null value is considered separately. """ from yaql.language import specs @specs.name('*equal') def eq(left, right): """:yaql:operator = Returns true if left and right are equal, false otherwise. It is system function and can be used to override behavior of comparison between objects. """ return left == right @specs.name('*not_equal') def neq(left, right): """:yaql:operator != Returns true if left and right are not equal, false otherwise. It is system function and can be used to override behavior of comparison between objects. """ return left != right @specs.parameter('right', type(None), nullable=True) @specs.parameter('left', nullable=False) @specs.name('#operator_<') def left_lt_null(left, right): """:yaql:operator < Returns false. This function is called when left is not null and right is null. :signature: left < right :arg left: left operand :argType left: not null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> 1 < null false """ return False @specs.parameter('right', type(None), nullable=True) @specs.parameter('left', nullable=False) @specs.name('#operator_<=') def left_lte_null(left, right): """:yaql:operator <= Returns false. This function is called when left is not null and right is null. :signature: left <= right :arg left: left operand :argType left: not null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> 1 <= null false """ return False @specs.parameter('right', type(None), nullable=True) @specs.parameter('left', nullable=False) @specs.name('#operator_>') def left_gt_null(left, right): """:yaql:operator > Returns true. This function is called when left is not null and right is null. :signature: left > right :arg left: left operand :argType left: not null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> 1 > null true """ return True @specs.parameter('right', type(None), nullable=True) @specs.parameter('left', nullable=False) @specs.name('#operator_>=') def left_gte_null(left, right): """:yaql:operator >= Returns true. This function is called when left is not null and right is null. :signature: left >= right :arg left: left operand :argType left: not null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> 1 >= null true """ return True @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', nullable=False) @specs.name('#operator_<') def null_lt_right(left, right): """:yaql:operator < Returns true. This function is called when left is null and right is not. :signature: left < right :arg left: left operand :argType left: null :arg right: right operand :argType right: not null :returnType: boolean .. code: yaql> null < 2 true """ return True @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', nullable=False) @specs.name('#operator_<=') def null_lte_right(left, right): """:yaql:operator <= Returns true. This function is called when left is null and right is not. :signature: left <= right :arg left: left operand :argType left: null :arg right: right operand :argType right: not null :returnType: boolean .. code: yaql> null <= 2 true """ return True @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', nullable=False) @specs.name('#operator_>') def null_gt_right(left, right): """:yaql:operator > Returns false. This function is called when left is null and right is not. :signature: left > right :arg left: left operand :argType left: null :arg right: right operand :argType right: not null :returnType: boolean .. code: yaql> null > 2 false """ return False @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', nullable=False) @specs.name('#operator_>=') def null_gte_right(left, right): """:yaql:operator >= Returns false. This function is called when left is null and right is not. :signature: left >= right :arg left: left operand :argType left: null :arg right: right operand :argType right: not null :returnType: boolean .. code: yaql> null >= 2 false """ return False @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', type(None), nullable=True) @specs.name('#operator_<') def null_lt_null(left, right): """:yaql:operator < Returns false. This function is called when left and right are null. :signature: left < right :arg left: left operand :argType left: null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> null < null false """ return False @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', type(None), nullable=True) @specs.name('#operator_<=') def null_lte_null(left, right): """:yaql:operator <= Returns true. This function is called when left and right are null. :signature: left <= right :arg left: left operand :argType left: null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> null <= null true """ return True @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', type(None), nullable=True) @specs.name('#operator_>') def null_gt_null(left, right): """:yaql:operator > Returns false. This function is called when left and right are null. :signature: left > right :arg left: left operand :argType left: null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> null > null false """ return False @specs.parameter('left', type(None), nullable=True) @specs.parameter('right', type(None), nullable=True) @specs.name('#operator_>=') def null_gte_null(left, right): """:yaql:operator >= Returns true. This function is called when left and right are null. :signature: left >= right :arg left: left operand :argType left: null :arg right: right operand :argType right: null :returnType: boolean .. code: yaql> null >= null true """ return True def register(context): context.register_function(eq) context.register_function(neq) context.register_function(left_lt_null) context.register_function(left_lte_null) context.register_function(left_gt_null) context.register_function(left_gte_null) context.register_function(null_lt_right) context.register_function(null_lte_right) context.register_function(null_gt_right) context.register_function(null_gte_right) context.register_function(null_lt_null) context.register_function(null_lte_null) context.register_function(null_gt_null) context.register_function(null_gte_null) yaql-1.1.3/yaql/standard_library/date_time.py000066400000000000000000000727321305545650400213030ustar00rootroot00000000000000# 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. """ The module describes which operations can be done with datetime objects. """ import datetime import time as python_time from yaql.language import specs from yaql.language import yaqltypes from dateutil import parser from dateutil import tz DATETIME_TYPE = datetime.datetime TIMESPAN_TYPE = datetime.timedelta ZERO_TIMESPAN = datetime.timedelta() UTCTZ = yaqltypes.DateTime.utctz def _get_tz(offset): if offset is None: return None if offset == ZERO_TIMESPAN: return UTCTZ return tz.tzoffset(None, seconds(offset)) @specs.name('datetime') @specs.parameter('year', int) @specs.parameter('month', int) @specs.parameter('day', int) @specs.parameter('hour', int) @specs.parameter('minute', int) @specs.parameter('second', int) @specs.parameter('microsecond', int) @specs.parameter('offset', TIMESPAN_TYPE) def build_datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, offset=ZERO_TIMESPAN): """:yaql:datetime Returns datetime object built on year, month, day, hour, minute, second, microsecond, offset. :signature: datetime(year, month, day, hour => 0, minute => 0, second => 0, microsecond => 0, offset => timespan(0)) :arg year: number of years in datetime :argType year: integer between 1 and 9999 inclusive :arg month: number of months in datetime :argType month: integer between 1 and 12 inclusive :arg day: number of days in datetime :argType day: integer between 1 and number of days in given month :arg hour: number of hours in datetime, 0 by default :argType hour: integer between 0 and 23 inclusive :arg minute: number of minutes in datetime, 0 by default :argType minute: integer between 0 and 59 inclusive :arg second: number of seconds in datetime, 0 by default :argType second: integer between 0 and 59 inclusive :arg microsecond: number of microseconds in datetime, 0 by default :argType microsecond: integer between 0 and 1000000-1 :arg offset: datetime offset in microsecond resolution, needed for tzinfo, timespan(0) by default :argType offset: timespan type :returnType: datetime object .. code:: yaql> let(datetime(2015, 9, 29)) -> [$.year, $.month, $.day] [2015, 9, 29] """ zone = _get_tz(offset) return DATETIME_TYPE(year, month, day, hour, minute, second, microsecond, zone) @specs.name('datetime') @specs.parameter('timestamp', yaqltypes.Number()) @specs.parameter('offset', TIMESPAN_TYPE) def datetime_from_timestamp(timestamp, offset=ZERO_TIMESPAN): """:yaql:datetime Returns datetime object built by timestamp. :signature: datetime(timestamp, offset => timespan(0)) :arg timestamp: timespan object to represent datetime :argType timestamp: number :arg offset: datetime offset in microsecond resolution, needed for tzinfo, timespan(0) by default :argType offset: timespan type :returnType: datetime object .. code:: yaql> let(datetime(1256953732)) -> [$.year, $.month, $.day] [2009, 10, 31] """ zone = _get_tz(offset) return DATETIME_TYPE.fromtimestamp(timestamp, tz=zone) @specs.name('datetime') @specs.parameter('string', yaqltypes.String()) @specs.parameter('format__', yaqltypes.String(True)) def datetime_from_string(string, format__=None): """:yaql:datetime Returns datetime object built by string parsed with format. :signature: datetime(string, format => null) :arg string: string representing datetime :argType string: string :arg format: format for parsing input string which should be supported with C99 standard of format codes. null by default, which means parsing with Python dateutil.parser usage :argType format: string :returnType: datetime object .. code:: yaql> let(datetime("29.8?2015")) -> [$.year, $.month, $.day] [2015, 8, 29] yaql> let(datetime("29.8?2015", "%d.%m?%Y"))->[$.year, $.month, $.day] [2015, 8, 29] """ if not format__: result = parser.parse(string) else: result = DATETIME_TYPE.strptime(string, format__) if not result.tzinfo: return result.replace(tzinfo=UTCTZ) return result @specs.name('timespan') @specs.parameter('days', int) @specs.parameter('hours', int) @specs.parameter('minutes', int) @specs.parameter('seconds', yaqltypes.Integer()) @specs.parameter('milliseconds', yaqltypes.Integer()) @specs.parameter('microseconds', yaqltypes.Integer()) def build_timespan(days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0): """:yaql:timespan Returns timespan object with specified args. :signature: timespan(days => 0, hours => 0, minutes => 0, seconds => 0, milliseconds => 0, microseconds => 0) :arg days: number of days in timespan, 0 by default :argType days: integer :arg hours: number of hours in timespan, 0 by default :argType hours: integer :arg minutes: number of minutes in timespan, 0 by default :argType minutes: integer :arg seconds: number of seconds in timespan, 0 by default :argType seconds: integer :arg milliseconds: number of microseconds in timespan, 0 by default :argType milliseconds: integer :arg microsecond: number of microseconds in timespan, 0 by default :argType microsecond: integer :returnType: timespan object .. code:: yaql> timespan(days => 1, hours => 2, minutes => 3).hours 26.05 """ return TIMESPAN_TYPE( days=days, hours=hours, minutes=minutes, seconds=seconds, milliseconds=milliseconds, microseconds=microseconds) @specs.yaql_property(TIMESPAN_TYPE) def microseconds(timespan): """:yaql:property microseconds Returns total microseconds in timespan. :signature: timespan.microseconds :returnType: integer .. code:: yaql> timespan(seconds => 1).microseconds 1000000 """ return (86400000000 * timespan.days + 1000000 * timespan.seconds + timespan.microseconds) @specs.yaql_property(TIMESPAN_TYPE) def milliseconds(timespan): """:yaql:property milliseconds Returns total milliseconds in timespan. :signature: timespan.milliseconds :returnType: float .. code:: yaql> timespan(seconds => 1).milliseconds 1000.0 """ return microseconds(timespan) / 1000.0 @specs.yaql_property(TIMESPAN_TYPE) def seconds(timespan): """:yaql:property seconds Returns total seconds in timespan. :signature: timespan.seconds :returnType: float .. code:: yaql> timespan(minutes => 1).seconds 60.0 """ return microseconds(timespan) / 1000000.0 @specs.yaql_property(TIMESPAN_TYPE) def minutes(timespan): """:yaql:property minutes Returns total minutes in timespan. :signature: timespan.minutes :returnType: float .. code:: yaql> timespan(hours => 2).minutes 120.0 """ return microseconds(timespan) / 60000000.0 @specs.yaql_property(TIMESPAN_TYPE) def hours(timespan): """:yaql:property hours Returns total hours in timespan. :signature: timespan.hours :returnType: float .. code:: yaql> timespan(days => 2).hours 48.0 """ return microseconds(timespan) / 3600000000.0 @specs.yaql_property(TIMESPAN_TYPE) def days(timespan): """:yaql:property days Returns total days in timespan. :signature: timespan.days :returnType: float .. code:: yaql> timespan(days => 2, hours => 48).days 4.0 """ return microseconds(timespan) / 86400000000.0 def now(offset=ZERO_TIMESPAN): """:yaql:now Returns the current local date and time. :signature: now(offset => timespan(0)) :arg offset: datetime offset in microsecond resolution, needed for tzinfo, timespan(0) by default :argType offset: timespan type :returnType: datetime .. code:: yaql> let(now()) -> [$.year, $.month, $.day] [2016, 7, 18] yaql> now(offset=>localtz()).hour - now().hour 3 """ zone = _get_tz(offset) return DATETIME_TYPE.now(tz=zone) def localtz(): """:yaql:localtz Returns local time zone in timespan object. :signature: localtz() :returnType: timespan object .. code:: yaql> localtz().hours 3.0 """ if python_time.daylight: return TIMESPAN_TYPE(seconds=-python_time.altzone) else: return TIMESPAN_TYPE(seconds=-python_time.timezone) def utctz(): """:yaql:utctz Returns UTC time zone in timespan object. :signature: utctz() :returnType: timespan object .. code:: yaql> utctz().hours 0.0 """ return ZERO_TIMESPAN @specs.name('#operator_+') @specs.parameter('dt', yaqltypes.DateTime()) @specs.parameter('ts', TIMESPAN_TYPE) def datetime_plus_timespan(dt, ts): """:yaql:operator + Returns datetime object with added timespan. :signature: left + right :arg left: input datetime object :argType left: datetime object :arg right: input timespan object :argType right: timespan object :returnType: datetime object .. code:: yaql> let(now() + timespan(days => 100)) -> $.month 10 """ return dt + ts @specs.name('#operator_+') @specs.parameter('ts', TIMESPAN_TYPE) @specs.parameter('dt', yaqltypes.DateTime()) def timespan_plus_datetime(ts, dt): """:yaql:operator + Returns datetime object with added timespan. :signature: left + right :arg left: input timespan object :argType left: timespan object :arg right: input datetime object :argType right: datetime object :returnType: datetime object .. code:: yaql> let(timespan(days => 100) + now()) -> $.month 10 """ return ts + dt @specs.name('#operator_-') @specs.parameter('dt', yaqltypes.DateTime()) @specs.parameter('ts', TIMESPAN_TYPE) def datetime_minus_timespan(dt, ts): """:yaql:operator - Returns datetime object with subtracted timespan. :signature: left - right :arg left: input datetime object :argType left: datetime object :arg right: input timespan object :argType right: timespan object :returnType: datetime object .. code:: yaql> let(now() - timespan(days => 100)) -> $.month 4 """ return dt - ts @specs.name('#operator_-') @specs.parameter('dt1', yaqltypes.DateTime()) @specs.parameter('dt2', yaqltypes.DateTime()) def datetime_minus_datetime(dt1, dt2): """:yaql:operator - Returns datetime object dt1 with subtracted dt2. :signature: left - right :arg left: input datetime object :argType left: datetime object :arg right: datetime object to be subtracted :argType right: datetime object :returnType: timespan object .. code:: yaql> let(now() - now()) -> $.microseconds -325 """ return dt1 - dt2 @specs.name('#operator_+') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def timespan_plus_timespan(ts1, ts2): """:yaql:operator + Returns sum of two timespan objects. :signature: left + right :arg left: input timespan object :argType left: timespan object :arg right: input timespan object :argType right: timespan object :returnType: timespan object .. code:: yaql> let(timespan(days => 1) + timespan(hours => 12)) -> $.hours 36.0 """ return ts1 + ts2 @specs.name('#operator_-') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def timespan_minus_timespan(ts1, ts2): """:yaql:operator - Returns timespan object with subtracted another timespan object. :signature: left - right :arg left: input timespan object :argType left: timespan object :arg right: input timespan object :argType right: timespan object :returnType: timespan object .. code:: yaql> let(timespan(days => 1) - timespan(hours => 12)) -> $.hours 12.0 """ return ts1 - ts2 @specs.name('#operator_>') @specs.parameter('dt1', yaqltypes.DateTime()) @specs.parameter('dt2', yaqltypes.DateTime()) def datetime_gt_datetime(dt1, dt2): """:yaql:operator > Returns true if left datetime is strictly greater than right datetime, false otherwise. :signature: left > right :arg left: left datetime object :argType left: datetime object :arg right: right datetime object :argType right: datetime object :returnType: boolean .. code:: yaql> datetime(2011, 11, 11) > datetime(2010, 10, 10) true """ return dt1 > dt2 @specs.name('#operator_>=') @specs.parameter('dt1', yaqltypes.DateTime()) @specs.parameter('dt2', yaqltypes.DateTime()) def datetime_gte_datetime(dt1, dt2): """:yaql:operator >= Returns true if left datetime is greater or equal to right datetime, false otherwise. :signature: left >= right :arg left: left datetime object :argType left: datetime object :arg right: right datetime object :argType right: datetime object :returnType: boolean .. code:: yaql> datetime(2011, 11, 11) >= datetime(2011, 11, 11) true """ return dt1 >= dt2 @specs.name('#operator_<') @specs.parameter('dt1', yaqltypes.DateTime()) @specs.parameter('dt2', yaqltypes.DateTime()) def datetime_lt_datetime(dt1, dt2): """:yaql:operator < Returns true if left datetime is strictly less than right datetime, false otherwise. :signature: left < right :arg left: left datetime object :argType left: datetime object :arg right: right datetime object :argType right: datetime object :returnType: boolean .. code:: yaql> datetime(2011, 11, 11) < datetime(2011, 11, 11) false """ return dt1 < dt2 @specs.name('#operator_<=') @specs.parameter('dt1', yaqltypes.DateTime()) @specs.parameter('dt2', yaqltypes.DateTime()) def datetime_lte_datetime(dt1, dt2): """:yaql:operator <= Returns true if left datetime is less or equal to right datetime, false otherwise. :signature: left <= right :arg left: left datetime object :argType left: datetime object :arg right: right datetime object :argType right: datetime object :returnType: boolean .. code:: yaql> datetime(2011, 11, 11) <= datetime(2011, 11, 11) true """ return dt1 <= dt2 @specs.name('#operator_>') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def timespan_gt_timespan(ts1, ts2): """:yaql:operator > Returns true if left timespan is strictly greater than right timespan, false otherwise. :signature: left > right :arg left: left timespan object :argType left: timespan object :arg right: right timespan object :argType right: timespan object :returnType: boolean .. code:: yaql> timespan(hours => 2) > timespan(hours => 1) true """ return ts1 > ts2 @specs.name('#operator_>=') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def timespan_gte_timespan(ts1, ts2): """:yaql:operator >= Returns true if left timespan is greater or equal to right timespan, false otherwise. :signature: left >= right :arg left: left timespan object :argType left: timespan object :arg right: right timespan object :argType right: timespan object :returnType: boolean .. code:: yaql> timespan(hours => 24) >= timespan(days => 1) true """ return ts1 >= ts2 @specs.name('#operator_<') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def timespan_lt_timespan(ts1, ts2): """:yaql:operator < Returns true if left timespan is strictly less than right timespan, false otherwise. :signature: left < right :arg left: left timespan object :argType left: timespan object :arg right: right timespan object :argType right: timespan object :returnType: boolean .. code:: yaql> timespan(hours => 23) < timespan(days => 1) true """ return ts1 < ts2 @specs.name('#operator_<=') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def timespan_lte_timespan(ts1, ts2): """:yaql:operator <= Returns true if left timespan is less or equal to right timespan, false otherwise. :signature: left <= right :arg left: left timespan object :argType left: timespan object :arg right: right timespan object :argType right: timespan object :returnType: boolean .. code:: yaql> timespan(hours => 23) <= timespan(days => 1) true """ return ts1 <= ts2 @specs.name('#operator_*') @specs.parameter('ts', TIMESPAN_TYPE) @specs.parameter('n', yaqltypes.Number()) def timespan_by_num(ts, n): """:yaql:operator * Returns timespan object built on timespan multiplied by number. :signature: left * right :arg left: timespan object :argType left: timespan object :arg right: number to multiply timespan :argType right: number :returnType: timespan .. code:: yaql> let(timespan(hours => 24) * 2) -> $.hours 48.0 """ return TIMESPAN_TYPE(microseconds=(microseconds(ts) * n)) @specs.name('#operator_*') @specs.parameter('n', yaqltypes.Number()) @specs.parameter('ts', TIMESPAN_TYPE) def num_by_timespan(n, ts): """:yaql:operator * Returns timespan object built on number multiplied by timespan. :signature: left * right :arg left: number to multiply timespan :argType left: number :arg right: timespan object :argType right: timespan object :returnType: timespan .. code:: yaql> let(2 * timespan(hours => 24)) -> $.hours 48.0 """ return TIMESPAN_TYPE(microseconds=(microseconds(ts) * n)) @specs.name('#operator_/') @specs.parameter('ts1', TIMESPAN_TYPE) @specs.parameter('ts2', TIMESPAN_TYPE) def div_timespans(ts1, ts2): """:yaql:operator / Returns result of division of timespan microseconds by another timespan microseconds. :signature: left / right :arg left: left timespan object :argType left: timespan object :arg right: right timespan object :argType right: timespan object :returnType: float .. code:: yaql> timespan(hours => 24) / timespan(hours => 12) 2.0 """ return (0.0 + microseconds(ts1)) / microseconds(ts2) @specs.name('#operator_/') @specs.parameter('ts', TIMESPAN_TYPE) @specs.parameter('n', yaqltypes.Number()) def div_timespan_by_num(ts, n): """:yaql:operator / Returns timespan object divided by number. :signature: left / right :arg left: left timespan object :argType left: timespan object :arg right: number to divide by :argType right: number :returnType: timespan object .. code:: yaql> let(timespan(hours => 24) / 2) -> $.hours 12.0 """ return TIMESPAN_TYPE(microseconds=(microseconds(ts) / n)) @specs.name('#unary_operator_-') @specs.parameter('ts', TIMESPAN_TYPE) def negative_timespan(ts): """:yaql:operator unary - Returns negative timespan. :signature: -arg :arg arg: input timespan object :argType arg: timespan object :returnType: timespan object .. code:: yaql> let(-timespan(hours => 24)) -> $.hours -24.0 """ return -ts @specs.name('#unary_operator_+') @specs.parameter('ts', TIMESPAN_TYPE) def positive_timespan(ts): """:yaql:operator unary + Returns timespan. :signature: +arg :arg arg: input timespan object :argType arg: timespan object :returnType: timespan object .. code:: yaql> let(+timespan(hours => -24)) -> $.hours -24.0 """ return ts @specs.yaql_property(DATETIME_TYPE) def year(dt): """:yaql:property year Returns year of given datetime. :signature: datetime.year :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30).year 2006 """ return dt.year @specs.yaql_property(DATETIME_TYPE) def month(dt): """:yaql:property month Returns month of given datetime. :signature: datetime.month :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30).month 11 """ return dt.month @specs.yaql_property(DATETIME_TYPE) def day(dt): """:yaql:property day Returns day of given datetime. :signature: datetime.day :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30).day 21 """ return dt.day @specs.yaql_property(DATETIME_TYPE) def hour(dt): """:yaql:property hour Returns hour of given datetime. :signature: datetime.hour :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30).hour 16 """ return dt.hour @specs.yaql_property(DATETIME_TYPE) def minute(dt): """:yaql:property minute Returns minutes of given datetime. :signature: datetime.minute :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30).minute 30 """ return dt.minute @specs.yaql_property(DATETIME_TYPE) def second(dt): """:yaql:property minute Returns seconds of given datetime. :signature: datetime.second :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30, 2).second 2 """ return dt.second @specs.yaql_property(DATETIME_TYPE) def microsecond(dt): """:yaql:property microsecond Returns microseconds of given datetime. :signature: datetime.microsecond :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30, 2, 123).microsecond 123 """ return dt.microsecond @specs.yaql_property(yaqltypes.DateTime()) def date(dt): """:yaql:property date Returns datetime object with only year, month, day and tzinfo part of given datetime. :signature: datetime.date :returnType: datetime object .. code:: yaql> let(datetime(2006, 11, 21, 16, 30, 2, 123).date) -> [$.year, $.month, $.day, $.hour] [2006, 11, 21, 0] """ return DATETIME_TYPE( year=dt.year, month=dt.month, day=dt.day, tzinfo=dt.tzinfo) @specs.yaql_property(yaqltypes.DateTime()) def time(dt): """:yaql:property time Returns timespan object built on datetime without year, month, day and tzinfo part of it. :signature: datetime.time :returnType: timespan object .. code:: yaql> let(datetime(2006, 11, 21, 16, 30).time) -> [$.hours, $.minutes] [16.5, 990.0] """ return dt - date(dt) @specs.yaql_property(DATETIME_TYPE) def weekday(dt): """:yaql:property weekday Returns the day of the week as an integer, Monday is 0 and Sunday is 6. :signature: datetime.weekday :returnType: integer .. code:: yaql> datetime(2006, 11, 21, 16, 30).weekday 1 """ return dt.weekday() @specs.yaql_property(yaqltypes.DateTime()) def utc(dt): """:yaql:property utc Returns datetime converted to UTC. :signature: datetime.utc :returnType: datetime object .. code:: yaql> datetime(2006, 11, 21, 16, 30, offset => timespan(hours => 3)).utc.hour 13 """ return dt - dt.utcoffset() @specs.yaql_property(DATETIME_TYPE) def offset(dt): """:yaql:property offset Returns offset of local time from UTC. :signature: datetime.offset :returnType: timespan .. code:: yaql> datetime(2006, 11, 21, 16, 30, offset => timespan(hours => 3)).offset.hours 3.0 """ return dt.utcoffset() or ZERO_TIMESPAN @specs.yaql_property(DATETIME_TYPE) def timestamp(dt): """:yaql:property timestamp Returns total seconds from datetime(1970, 1, 1) to datetime UTC. :signature: datetime.timestamp :returnType: float .. code:: yaql> datetime(2006, 11, 21, 16, 30).timestamp 1164126600.0 """ return (utc(dt) - DATETIME_TYPE(1970, 1, 1, tzinfo=UTCTZ)).total_seconds() @specs.method @specs.parameter('dt', yaqltypes.DateTime()) @specs.parameter('year', int) @specs.parameter('month', int) @specs.parameter('day', int) @specs.parameter('hour', int) @specs.parameter('minute', int) @specs.parameter('second', int) @specs.parameter('microsecond', int) @specs.parameter('offset', TIMESPAN_TYPE) def replace(dt, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, offset=None): """:yaql:replace Returns datetime object with applied replacements. :signature: dt.replace(year => null, month => null, day => null, hour => null, minute => null, second => null, microsecond => null, offset => null) :receiverArg dt: input datetime object :argType dt: datetime object :arg year: number of years to replace, null by default which means no replacement :argType year: integer between 1 and 9999 inclusive :arg month: number of months to replace, null by default which means no replacement :argType month: integer between 1 and 12 inclusive :arg day: number of days to replace, null by default which means no replacement :argType day: integer between 1 and number of days in given month :arg hour: number of hours to replace, null by default which means no replacement :argType hour: integer between 0 and 23 inclusive :arg minute: number of minutes to replace, null by default which means no replacement :argType minute: integer between 0 and 59 inclusive :arg second: number of seconds to replace, null by default which means no replacement :argType second: integer between 0 and 59 inclusive :arg microsecond: number of microseconds to replace, null by default which means no replacement :argType microsecond: integer between 0 and 1000000-1 :arg offset: datetime offset in microsecond resolution to replace, null by default which means no replacement :argType offset: timespan type :returnType: datetime object .. code:: yaql> datetime(2015, 9, 29).replace(year => 2014).year 2014 """ replacements = {} if year is not None: replacements['year'] = year if month is not None: replacements['month'] = month if day is not None: replacements['day'] = day if hour is not None: replacements['hour'] = hour if minute is not None: replacements['minute'] = minute if second is not None: replacements['second'] = second if microsecond is not None: replacements['microsecond'] = microsecond if offset is not None: replacements['tzinfo'] = _get_tz(offset) return dt.replace(**replacements) @specs.method @specs.parameter('dt', yaqltypes.DateTime()) @specs.parameter('format__', yaqltypes.String()) def format_(dt, format__): """:yaql:format Returns a string representing datetime, controlled by a format string. :signature: dt.format(format) :receiverArg dt: input datetime object :argType dt: datetime object :arg format: format string :argType format: string :returnType: string .. code:: yaql> now().format("%A, %d. %B %Y %I:%M%p") "Tuesday, 19. July 2016 08:49AM" """ return dt.strftime(format__) def is_datetime(value): """:yaql:isDatetime Returns true if value is datetime object, false otherwise. :signature: isDatetime(value) :arg value: input value :argType value: any :returnType: boolean .. code:: yaql> isDatetime(now()) true yaql> isDatetime(datetime(2010, 10, 10)) true """ return isinstance(value, DATETIME_TYPE) def is_timespan(value): """:yaql:isTimespan Returns true if value is timespan object, false otherwise. :signature: isTimespan(value) :arg value: input value :argType value: any :returnType: boolean .. code:: yaql> isTimespan(now()) false yaql> isTimespan(timespan()) true """ return isinstance(value, TIMESPAN_TYPE) def register(context): functions = ( build_datetime, build_timespan, datetime_from_timestamp, datetime_from_string, now, localtz, utctz, utc, days, hours, minutes, seconds, milliseconds, microseconds, datetime_plus_timespan, timespan_plus_datetime, datetime_minus_timespan, datetime_minus_datetime, timespan_plus_timespan, timespan_minus_timespan, datetime_gt_datetime, datetime_gte_datetime, datetime_lt_datetime, datetime_lte_datetime, timespan_gt_timespan, timespan_gte_timespan, timespan_lt_timespan, timespan_lte_timespan, negative_timespan, positive_timespan, timespan_by_num, num_by_timespan, div_timespans, div_timespan_by_num, year, month, day, hour, minute, second, microsecond, weekday, offset, timestamp, date, time, replace, format_, is_datetime, is_timespan ) for func in functions: context.register_function(func) yaql-1.1.3/yaql/standard_library/legacy.py000066400000000000000000000172441305545650400206110ustar00rootroot00000000000000# 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. """ The module describes functions which are available with backward compatibility mode with YAQL v0.2. Examples are provided with CLI started with legacy mode. """ import itertools import six from yaql.language import contexts from yaql.language import expressions from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes @specs.parameter('left', yaqltypes.YaqlExpression()) @specs.name('#operator_=>') def build_tuple(left, right, context, engine): """:yaql:operator => Returns tuple. :signature: left => right :arg left: left value for tuple :argType left: any :arg right: right value for tuple :argType right: any :returnType: tuple .. code:: yaql> a => b ["a", "b"] yaql> null => 1 => [] [null, 1, []] """ if isinstance(left, expressions.BinaryOperator) and left.operator == '=>': return left(utils.NO_VALUE, context, engine) + (right,) else: return left(utils.NO_VALUE, context, engine), right @specs.parameter('tuples', tuple) @specs.inject('delegate', yaqltypes.Super(with_name=True)) @specs.no_kwargs @specs.extension_method def dict_(delegate, *tuples): """:yaql:dict Returns dict built from tuples. :signature: dict([args]) :arg [args]: chain of tuples to be interpreted as (key, value) for dict :argType [args]: chain of tuples :returnType: dictionary .. code:: yaql> dict(a => 1, b => 2) {"a": 1, "b": 2} yaql> dict(tuple(a, 1), tuple(b, 2)) {"a": 1, "b": 2} """ return delegate('dict', tuples) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def to_list(collection): """:yaql:toList Returns collection converted to list. :signature: collection.toList() :receiverArg collection: collection to be converted :argType collection: iterable :returnType: list .. code:: yaql> range(0, 3).toList() [0, 1, 2] """ return list(collection) def tuple_(*args): """:yaql:tuple Returns tuple of args. :signature: tuple([args]) :arg [args]: chain of values for tuple :argType [args]: chain of any types :returnType: tuple .. code:: yaql> tuple(0, [], "a") [0, [], "a"] """ return args @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('index_expression', yaqltypes.Lambda()) def indexer(collection, index_expression): if isinstance(collection, utils.SequenceType): index = index_expression() if isinstance(index, int) and not isinstance(index, bool): return collection[index] return six.moves.filter(index_expression, collection) @specs.parameter('start', int) @specs.parameter('stop', int, nullable=True) @specs.extension_method def range_(start, stop=None): """:yaql:range Returns sequence from [start, stop). :signature: start.range(stop => null) :receiverArg start: value to start from :argType start: integer :arg stop: value to end with. null by default, which means returning iterator to sequence :argType stop: integer :returnType: sequence .. code:: yaql> 0.range(3) [0, 1, 2] yaql> 0.range().take(4) [0, 1, 2, 3] """ if stop is None: return itertools.count(start) else: return six.moves.range(start, stop) @specs.parameter('conditions', yaqltypes.Lambda(with_context=True)) @specs.no_kwargs @specs.extension_method def switch(value, context, *conditions): """:yaql:switch Returns the value of the first key-value pair for which condition returned true. If there is no such returns null. :signature: value.switch([args]) :receiverArg value: value to be used evaluating conditions :argType value: any type :arg [args]: conditions to be checked for the first true :argType [args]: chain of mappings :returnType: any (appropriate value type) .. code:: yaql> 15.switch($ < 3 => "a", $ < 7 => "b", $ => "c") "c" """ context = context.create_child_context() context[''] = value for cond in conditions: res = cond(context) if isinstance(res, tuple): if len(res) != 2: raise ValueError('switch() tuples must be of size 2') if res[0]: return res[1] elif isinstance(res, utils.MappingRule): if res.source: return res.destination else: raise ValueError('switch() must have tuple or mapping parameters') return None @specs.parameter('receiver', contexts.ContextBase) @specs.parameter('expr', yaqltypes.Lambda(with_context=True, method=True)) @specs.name('#operator_.') def op_dot_context(receiver, expr): return expr(receiver['$0'], receiver) @specs.parameter('mappings', yaqltypes.Lambda()) @specs.method @specs.no_kwargs def as_(context, receiver, *mappings): """:yaql:as Returns context object with mapping functions applied on receiver and passed under corresponding keys. :signature: receiver.as([args]) :receiverArg receiver: value to be used for mappings lambdas evaluating :argType receiver: any type :arg [args]: tuples with lambdas and appropriate keys to be passed to context :argType [args]: chain of tuples :returnType: context object .. code:: yaql> [1, 2].as(len($) => a, sum($) => b) -> $a + $b 5 """ for t in mappings: tt = t(receiver) if isinstance(tt, tuple): if len(tt) != 2: raise ValueError('as() tuples must be of size 2') context[tt[1]] = tt[0] elif isinstance(tt, utils.MappingRule): context[tt.destination] = tt.source else: raise ValueError('as() must have tuple parameters') context['$0'] = receiver return context @specs.parameter('d', utils.MappingType, alias='dict') @specs.parameter('key', yaqltypes.Keyword()) @specs.name('#operator_.') def dict_keyword_access(d, key): """:yaql:operator . Returns dict's key value. :signature: left.right :arg left: input dictionary :argType left: mapping :arg right: key :argType right: keyword :returnType: any (appropriate value type) .. code:: yaql> {a => 2, b => 2}.a 2 """ return d.get(key) def register(context, tuples): if tuples: context.register_function(build_tuple) context.register_function(to_list) context.register_function(tuple_) context.register_function(dict_) context.register_function(dict_, name='#map') context.register_function(indexer, name='#indexer', exclusive=True) context.register_function(range_) context.register_function(switch, exclusive=True) context.register_function(as_) context.register_function(op_dot_context) context.register_function(dict_keyword_access) for t in ('get', 'list', 'bool', 'int', 'float', 'select', 'where', 'join', 'sum', 'take_while'): for spec in utils.to_extension_method(t, context): context.register_function(spec) yaql-1.1.3/yaql/standard_library/math.py000066400000000000000000000354011305545650400202710ustar00rootroot00000000000000# 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. """ The Math module describes implemented math operations on numbers. """ import random import six from yaql.language import specs from yaql.language import yaqltypes @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_+') def binary_plus(left, right): """:yaql:operator + Returns the sum of left and right operands. :signature: left + right :arg left: left operand :argType left: number :arg right: right operand :argType right: number :returnType: number .. code:: yaql> 3 + 2 5 """ return left + right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_-') def binary_minus(left, right): """:yaql:operator - Returns the difference between left and right. :signature: left - right :arg left: left operand :argType left: number :arg right: right operand :argType right: number :returnType: number .. code:: yaql> 3 - 2 1 """ return left - right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_*') def multiplication(left, right): """:yaql:operator * Returns left multiplied by right. :signature: left * right :arg left: left operand :argType left: number :arg right: right operand :argType right: number :returnType: number .. code:: yaql> 3 * 2.5 7.5 """ return left * right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_/') def division(left, right): """:yaql:operator / Returns left divided by right. :signature: left / right :arg left: left operand :argType left: number :arg right: right operand :argType right: number :returnType: number .. code:: yaql> 3 / 2 1 yaql> 3.0 / 2 1.5 """ if isinstance(left, six.integer_types) and isinstance( right, six.integer_types): return left // right return left / right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_mod') def modulo(left, right): """:yaql:operator mod Returns left modulo right. :signature: left mod right :arg left: left operand :argType left: number :arg right: right operand :argType right: number :returnType: number .. code:: yaql> 3 mod 2 1 """ return left % right @specs.parameter('op', yaqltypes.Number()) @specs.name('#unary_operator_+') def unary_plus(op): """:yaql:operator unary + Returns +op. :signature: +op :arg op: operand :argType op: number :returnType: number .. code:: yaql> +2 2 """ return +op @specs.parameter('op', yaqltypes.Number()) @specs.name('#unary_operator_-') def unary_minus(op): """:yaql:operator unary - Returns -op. :signature: -op :arg op: operand :argType op: number :returnType: number .. code:: yaql> -2 -2 """ return -op @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_>') def gt(left, right): """:yaql:operator > Returns true if left is strictly greater than right, false otherwise. :signature: left > right :arg left: left operand :argType left: number :arg right: right operand :argType left: number :returnType: boolean .. code:: yaql> 3 > 2 true """ return left > right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_>=') def gte(left, right): """:yaql:operator >= Returns true if left is greater or equal to right, false otherwise. :signature: left >= right :arg left: left operand :argType left: number :arg right: right operand :argType left: number :returnType: boolean .. code:: yaql> 3 >= 3 true """ return left >= right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_<') def lt(left, right): """:yaql:operator < Returns true if left is strictly less than right, false otherwise. :signature: left < right :arg left: left operand :argType left: number :arg right: right operand :argType left: number :returnType: boolean .. code:: yaql> 3 < 2 false """ return left < right @specs.parameter('left', yaqltypes.Number()) @specs.parameter('right', yaqltypes.Number()) @specs.name('#operator_<=') def lte(left, right): """:yaql:operator <= Returns true if left is less or equal to right, false otherwise. :signature: left <= right :arg left: left operand :argType left: number :arg right: right operand :argType left: number :returnType: boolean .. code:: yaql> 3 <= 3 true """ return left <= right @specs.parameter('op', yaqltypes.Number()) def abs_(op): """:yaql:abs Returns the absolute value of a number. :signature: abs(op) :arg op: input value :argType op: number :returnType: number .. code:: yaql> abs(-2) 2 """ return abs(op) def int_(value): """:yaql:int Returns an integer built from number, string or null value. :signature: int(value) :arg value: input value :argType value: number, string or null :returnType: integer .. code:: yaql> int("2") 2 yaql> int(12.999) 12 yaql> int(null) 0 """ if value is None: return 0 return int(value) def float_(value): """:yaql:float Returns a floating number built from number, string or null value. :signature: float(value) :arg value: input value :argType value: number, string or null :returnType: float .. code:: yaql> float("2.2") 2.2 yaql> float(12) 12.0 yaql> float(null) 0.0 """ if value is None: return 0.0 return float(value) def random_(): """:yaql:random Returns the next random floating number from [0.0, 1.0). :signature: random() :returnType: float .. code:: yaql> random() 0.6039529924951869 """ return random.random() def random__(from_, to_): """:yaql:random Returns the next random integer from [a, b]. :signature: random(from, to) :arg from: left value for generating random number :argType from: integer :arg to: right value for generating random number :argType to: integer :returnType: integer .. code:: yaql> random(1, 2) 2 yaql> random(1, 2) 1 """ return random.randint(from_, to_) @specs.parameter('left', int) @specs.parameter('right', int) def bitwise_and(left, right): """:yaql:bitwiseAnd Returns applied "bitwise and" to left and right integers. Each bit of the output is 1 if the corresponding bit of left AND right is 1, otherwise 0. :signature: bitwiseAnd(left, right) :arg left: left value :argType left: integer :arg right: right value :argType right: integer :returnType: integer .. code:: yaql> bitwiseAnd(6, 12) 4 """ return left & right @specs.parameter('left', int) @specs.parameter('right', int) def bitwise_or(left, right): """:yaql:bitwiseOr Returns applied "bitwise or" to left and right numbers. Each bit of the output is 1 if the corresponding bit of left OR right is 1, otherwise 0. :signature: bitwiseOr(left, right) :arg left: left value :argType left: integer :arg right: right value :argType right: integer :returnType: integer .. code:: yaql> bitwiseOr(6, 12) 14 """ return left | right @specs.parameter('left', int) @specs.parameter('right', int) def bitwise_xor(left, right): """:yaql:bitwiseXor Returns applied "bitwise exclusive or" to left and right numbers. Each bit of the output is equal to the sum of corresponding left and right bits mod 2. :signature: bitwiseXor(left, right) :arg left: left value :argType left: integer :arg right: right value :argType right: integer :returnType: integer .. code:: yaql> bitwiseXor(6, 12) 10 """ return left ^ right @specs.parameter('arg', int) def bitwise_not(arg): """:yaql:bitwiseNot Returns an integer where each bit is a reversed corresponding bit of arg. :signature: bitwiseNot(arg) :arg arg: input value :argType arg: integer :returnType: integer .. code:: yaql> bitwiseNot(6) -7 """ return ~arg @specs.parameter('value', int) @specs.parameter('bits_number', int) def shift_bits_right(value, bits_number): """:yaql:shiftBitsRight Shifts the bits of value right by the number of bits bitsNumber. :signature: shiftBitsRight(value, bitsNumber) :arg value: given value :argType value: integer :arg bitsNumber: number of bits :argType right: integer :returnType: integer .. code:: yaql> shiftBitsRight(8, 2) 2 """ return value >> bits_number @specs.parameter('value', int) @specs.parameter('bits_number', int) def shift_bits_left(value, bits_number): """:yaql:shiftBitsLeft Shifts the bits of value left by the number of bits bitsNumber. :signature: shiftBitsLeft(value, bitsNumber) :arg value: given value :argType value: integer :arg bitsNumber: number of bits :argType right: integer :returnType: integer .. code:: yaql> shiftBitsLeft(8, 2) 32 """ return value << bits_number @specs.parameter('a', nullable=True) @specs.parameter('b', nullable=True) @specs.inject('operator', yaqltypes.Delegate('#operator_>')) def max_(a, b, operator): """:yaql:max Returns max from a and b. :signature: max(a, b) :arg a: input value :argType a: number :arg b: input value :argType b: number :returnType: number .. code:: yaql> max(8, 2) 8 """ if operator(b, a): return b return a @specs.inject('operator', yaqltypes.Delegate('#operator_>')) def min_(a, b, operator): """:yaql:min Returns min from a and b. :signature: min(a, b) :arg a: input value :argType a: number :arg b: input value :argType b: number :returnType: number .. code:: yaql> min(8, 2) 2 """ if operator(b, a): return a return b @specs.parameter('a', yaqltypes.Number()) @specs.parameter('b', yaqltypes.Number()) @specs.parameter('c', yaqltypes.Number(nullable=True)) def pow_(a, b, c=None): """:yaql:pow Returns a to the power b modulo c. :signature: pow(a, b, c => null) :arg a: input value :argType a: number :arg b: power :argType b: number :arg c: modulo. null by default, which means no modulo is done after power. :argType c: integer :returnType: number .. code:: yaql> pow(3, 2) 9 yaql> pow(3, 2, 5) 4 """ return pow(a, b, c) @specs.parameter('num', yaqltypes.Number()) def sign(num): """:yaql:sign Returns 1 if num > 0; 0 if num = 0; -1 if num < 0. :signature: sign(num) :arg num: input value :argType num: number :returnType: integer (-1, 0 or 1) .. code:: yaql> sign(2) 1 """ if num > 0: return 1 elif num < 0: return -1 return 0 @specs.parameter('number', yaqltypes.Number()) @specs.parameter('ndigits', int) def round_(number, ndigits=0): """:yaql:round Returns a floating number rounded to ndigits after the decimal point. :signature: round(number, ndigits => 0) :arg number: input value :argType number: number :arg ndigits: with how many digits after decimal point to round. 0 by default :argType ndigits: integer :returnType: number .. code:: yaql> round(12.52) 13 yaql> round(12.52, 1) 12.5 """ return round(number, ndigits) def is_integer(value): """:yaql:isInteger Returns true if value is an integer number, otherwise false. :signature: isInteger(value) :arg value: input value :argType value: any :returnType: boolean .. code:: yaql> isInteger(12.0) false yaql> isInteger(12) true """ return isinstance(value, six.integer_types) and not isinstance(value, bool) def is_number(value): """:yaql:isNumber Returns true if value is an integer or floating number, otherwise false. :signature: isNumber(value) :arg value: input value :argType value: any :returnType: boolean .. code:: yaql> isNumber(12.0) true yaql> isNumber(12) true """ return (isinstance(value, six.integer_types + (float,)) and not isinstance(value, bool)) def register(context): context.register_function(binary_plus) context.register_function(binary_minus) context.register_function(multiplication) context.register_function(division) context.register_function(modulo) context.register_function(unary_plus) context.register_function(unary_minus) context.register_function(abs_) context.register_function(gt) context.register_function(gte) context.register_function(lt) context.register_function(lte) context.register_function(int_) context.register_function(float_) context.register_function(random_) context.register_function(random__) context.register_function(bitwise_and) context.register_function(bitwise_or) context.register_function(bitwise_not) context.register_function(bitwise_xor) context.register_function(shift_bits_left) context.register_function(shift_bits_right) context.register_function(max_) context.register_function(min_) context.register_function(pow_) context.register_function(sign) context.register_function(round_) context.register_function(is_integer) context.register_function(is_number) yaql-1.1.3/yaql/standard_library/queries.py000066400000000000000000001516461305545650400210270ustar00rootroot00000000000000# 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. """ Queries module. """ import itertools import six from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes class OrderingIterable(utils.IterableType): def __init__(self, collection, operator_lt, operator_gt): self.collection = collection self.operator_lt = operator_lt self.operator_gt = operator_gt self.order = [] self.sorted = None def append_field(self, selector, is_ascending): self.order.append((selector, is_ascending)) def __iter__(self): if self.sorted is None: self.do_sort() return iter(self.sorted) def do_sort(outer_self): class Comparator(object): @staticmethod def compare(left, right): result = 0 for t in outer_self.order: a = t[0](left) b = t[0](right) if outer_self.operator_lt(a, b): result = -1 elif outer_self.operator_gt(a, b): result = 1 else: continue if not t[1]: result *= -1 break return result def __init__(self, obj): self.obj = obj def __lt__(self, other): return self.compare(self.obj, other.obj) < 0 def __gt__(self, other): return self.compare(self.obj, other.obj) > 0 def __eq__(self, other): return self.compare(self.obj, other.obj) == 0 def __le__(self, other): return self.compare(self.obj, other.obj) <= 0 def __ge__(self, other): return self.compare(self.obj, other.obj) >= 0 def __ne__(self, other): return self.compare(self.obj, other.obj) != 0 outer_self.sorted = sorted(outer_self.collection, key=Comparator) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) @specs.method def where(collection, predicate): """:yaql:where Returns only those collection elements, for which the filtering query (predicate) is true. :signature: collection.where(predicate) :receiverArg collection: collection to be filtered :argType collection: iterable :arg predicate: filter for collection elements :argType predicate: lambda :returnType: iterable .. code:: yaql> [1, 2, 3, 4, 5].where($ > 3) [4, 5] """ return six.moves.filter(predicate, collection) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('selector', yaqltypes.Lambda()) @specs.method def select(collection, selector): """:yaql:select Applies the selector to every item of the collection and returns a list of results. :signature: collection.select(selector) :receiverArg collection: input collection :argType collection: iterable :arg selector: expression for processing elements :argType selector: lambda :returnType: iterable .. code:: yaql> [1, 2, 3, 4, 5].select($ * $) [1, 4, 9, 16, 25] yaql> [{'a'=> 2}, {'a'=> 4}].select($.a) [2, 4] """ return six.moves.map(selector, collection) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('attribute', yaqltypes.Keyword(expand=False)) @specs.inject('operator', yaqltypes.Delegate('#operator_.')) @specs.name('#operator_.') def collection_attribution(collection, attribute, operator): """:yaql:operator . Retrieves the value of an attribute for each element in a collection and returns a list of results. :signature: collection.attribute :arg collection: input collection :argType collection: iterable :arg attribute: attribute to get on every collection item :argType attribute: keyword :returnType: list .. code:: yaql> [{"a" => 1}, {"a" => 2, "b" => 3}].a [1, 2] """ return six.moves.map( lambda t: operator(t, attribute), collection) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('count', int, nullable=False) @specs.method def skip(collection, count): """:yaql:skip Returns a collection without first count elements. :signature: collection.skip(count) :receiverArg collection: input collection :argType collection: iterable :arg count: how many elements to skip. If count is greater or equal to collection size, return value is empty list :argType count: integer :returnType: iterable .. code:: yaql> [1, 2, 3, 4, 5].skip(2) [3, 4, 5] """ return itertools.islice(collection, count, None) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('count', int, nullable=False) @specs.method def limit(collection, count): """:yaql:limit Returns the first count elements of a collection. :signature: collection.limit(count) :receiverArg collection: input collection :argType collection: iterable :arg count: how many first elements of a collection to return. If count is greater or equal to collection size, return value is input collection :argType count: integer :returnType: iterable .. code:: yaql> [1, 2, 3, 4, 5].limit(4) [1, 2, 3, 4] """ return itertools.islice(collection, count) @specs.parameter('collection', yaqltypes.Iterable()) @specs.extension_method def append(collection, *args): """:yaql:append Returns a collection with appended args. :signature: collection.append([args]) :receiverArg collection: input collection :argType collection: iterable :arg [args]: arguments to be appended to input collection :argType [args]: chain of any types :returnType: iterable .. code:: yaql> [1, 2, 3].append(4, 5) [1, 2, 3, 4, 5] """ for t in collection: yield t for t in args: yield t @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('key_selector', yaqltypes.Lambda()) @specs.extension_method def distinct(engine, collection, key_selector=None): """:yaql:distinct Returns only unique members of the collection. If keySelector is specified, it is used to determine uniqueness. :signature: collection.distinct(keySelector => null) :receiverArg collection: input collection :argType collection: iterable :arg keySelector: specifies a function of one argument that is used to extract a comparison key from each collection element. The default value is null, which means elements are compared directly :argType keySelector: lambda :returnType: iterable .. code:: yaql> [1, 2, 3, 1].distinct() [1, 2, 3] yaql> [{'a'=> 1}, {'b'=> 2}, {'a'=> 1}].distinct() [{"a": 1}, {"b": 2}] yaql> [['a', 1], ['b', 2], ['c', 1], ['a', 3]].distinct($[1]) [['a', 1], ['b', 2], ['a', 3]] """ distinct_values = set() for t in collection: key = t if key_selector is None else key_selector(t) if key not in distinct_values: distinct_values.add(key) utils.limit_memory_usage(engine, (1, distinct_values)) yield t @specs.parameter('collection', yaqltypes.Iterable()) @specs.extension_method def enumerate_(collection, start=0): """:yaql:enumerate Returns an iterator over pairs (index, value), obtained from iterating over a collection. :signature: collection.enumerate(start => 0) :receiverArg collection: input collection :argType collection: iterable :arg start: a value to start with numerating first element of each pair, 0 is a default value :argType start: integer :returnType: list .. code:: yaql> ['a', 'b', 'c'].enumerate() [[0, 'a'], [1, 'b'], [2, 'c']] yaql> ['a', 'b', 'c'].enumerate(2) [[2, 'a'], [3, 'b'], [4, 'c']] """ for i, t in enumerate(collection, start): yield [i, t] @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) @specs.extension_method def any_(collection, predicate=None): """:yaql:any Returns true if a collection is not empty. If a predicate is specified, determines whether any element of the collection satisfies the predicate. :signature: collection.any(predicate => null) :receiverArg collection: input collection :argType collection: iterable :arg predicate: lambda function to apply to every collection value. null by default, which means checking collection length :argType predicate: lambda :returnType: boolean .. code:: yaql> [[], 0, ''].any() true yaql> [[], 0, ''].any(predicate => $) false """ for t in collection: if predicate is None or predicate(t): return True return False @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) @specs.extension_method def all_(collection, predicate=None): """:yaql:all Returns true if all the elements of a collection evaluate to true. If a predicate is specified, returns true if the predicate is true for all elements in the collection. :signature: collection.all(predicate => null) :receiverArg collection: input collection :argType collection: iterable :arg predicate: lambda function to apply to every collection value. null by default, which means evaluating collections elements to boolean with no predicate :argType predicate: lambda :returnType: boolean .. code:: yaql> [1, [], ''].all() false yaql> [1, [0], 'a'].all() true """ if predicate is None: predicate = lambda x: bool(x) for t in collection: if not predicate(t): return False return True @specs.parameter('collections', yaqltypes.Iterable()) @specs.extension_method def concat(*collections): """:yaql:concat Returns an iterator that consequently iterates over elements of the first collection, then proceeds to the next collection and so on. :signature: collection.concat([args]) :receiverArg collection: input collection :argType collection: iterable :arg [args]: iterables to be concatenated with input collection :argType [args]: chain of iterable :returnType: iterable .. code:: yaql> [1].concat([2, 3], [4, 5]) [1, 2, 3, 4, 5] """ return itertools.chain(*collections) @specs.parameter('collection', utils.IteratorType) @specs.name('len') @specs.extension_method def count_(collection): """:yaql:len Returns the size of the collection. :signature: collection.len() :receiverArg collection: input collection :argType collection: iterable :returnType: integer .. code:: yaql> [1, 2].len() 2 """ count = 0 for t in collection: count += 1 return count @specs.parameter('collection', yaqltypes.Iterable()) @specs.method def count(collection): """:yaql:count Returns the size of the collection. :signature: collection.count() :receiverArg collection: input collection :argType collection: iterable :returnType: integer .. code:: yaql> [1, 2].count() 2 """ return count_(collection) @specs.parameter('collection', yaqltypes.Iterable()) @specs.method def memorize(collection, engine): """:yaql:memorize Returns an iterator over collection and memorizes already iterated values. This function can be used for iterating over collection several times as it remembers elements, and when given collection (iterator) is too large to be unwrapped at once. :signature: collection.memorize() :receiverArg collection: input collection :argType collection: iterable :returnType: iterator to collection .. code:: yaql> let(range(4)) -> $.sum() + $.len() 6 yaql> let(range(4).memorize()) -> $.sum() + $.len() 10 """ return utils.memorize(collection, engine) @specs.parameter('collection', yaqltypes.Iterable()) @specs.inject('operator', yaqltypes.Delegate('#operator_+')) @specs.method def sum_(operator, collection, initial=utils.NO_VALUE): """:yaql:sum Returns the sum of values in a collection starting from initial if specified. :signature: collection.sum(initial => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg initial: value to start sum with. NoValue by default :argType initial: collection's elements type :returnType: collection's elements type .. code:: yaql> [3, 1, 2].sum() 6 yaql> ['a', 'b'].sum('c') "cab" """ return aggregate(collection, operator, initial) @specs.parameter('collection', yaqltypes.Iterable()) @specs.inject('func', yaqltypes.Delegate('max')) @specs.method def max_(func, collection, initial=utils.NO_VALUE): """:yaql:max Returns max value in collection. Considers initial if specified. :signature: collection.max(initial => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg initial: value to start with. NoValue by default :argType initial: collection's elements type :returnType: collection's elements type .. code:: yaql> [3, 1, 2].max() 3 """ return aggregate(collection, func, initial) @specs.parameter('collection', yaqltypes.Iterable()) @specs.inject('func', yaqltypes.Delegate('min')) @specs.method def min_(func, collection, initial=utils.NO_VALUE): """:yaql:min Returns min value in collection. Considers initial if specified. :signature: collection.min(initial => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg initial: value to start with. NoValue by default :argType initial: collection's elements type :returnType: collection's elements type .. code:: yaql> [3, 1, 2].min() 1 """ return aggregate(collection, func, initial) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('default', nullable=True) @specs.method def first(collection, default=utils.NO_VALUE): """:yaql:first Returns the first element of the collection. If the collection is empty, returns the default value or raises StopIteration if default is not specified. :signature: collection.first(default => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg default: value to be returned if collection is empty. NoValue by default :argType default: any :returnType: type of collection's elements or default value type .. code:: yaql> [3, 1, 2].first() 3 """ try: return six.next(iter(collection)) except StopIteration: if default is utils.NO_VALUE: raise return default @specs.parameter('collection', yaqltypes.Iterable()) @specs.method def single(collection): """:yaql:single Checks that collection has only one element and returns it. If the collection is empty or has more than one element, raises StopIteration. :signature: collection.single() :receiverArg collection: input collection :argType collection: iterable :returnType: type of collection's elements .. code:: yaql> ["abc"].single() "abc" yaql> [1, 2].single() Execution exception: Collection contains more than one item """ it = iter(collection) result = six.next(it) try: six.next(it) except StopIteration: return result raise StopIteration('Collection contains more than one item') @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('default', nullable=True) @specs.method def last(collection, default=utils.NO_VALUE): """:yaql:last Returns the last element of the collection. If the collection is empty, returns the default value or raises StopIteration if default is not specified. :signature: collection.last(default => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg default: value to be returned if collection is empty. NoValue is default value. :argType default: any :returnType: type of collection's elements or default value type .. code:: yaql> [0, 1, 2].last() 2 """ if isinstance(collection, utils.SequenceType): if len(collection) == 0: if default is utils.NO_VALUE: raise StopIteration() else: return default return collection[-1] last_value = default for t in collection: last_value = t if last_value is utils.NO_VALUE: raise StopIteration() else: return last_value @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('selector', yaqltypes.Lambda()) @specs.method def select_many(collection, selector): """:yaql:selectMany Applies a selector to each element of the collection and returns an iterator over results. If the selector returns an iterable object, iterates over its elements instead of itself. :signature: collection.selectMany(selector) :receiverArg collection: input collection :argType collection: iterable :arg selector: function to be applied to every collection element :argType selector: lambda :returnType: iterator .. code:: yaql> [0, 1, 2].selectMany($ + 2) [2, 3, 4] yaql> [0, [1, 2], 3].selectMany($ * 2) [0, 1, 2, 1, 2, 6] """ for item in collection: inner = selector(item) if utils.is_iterable(inner): for t in inner: yield t else: yield inner @specs.parameter('stop', int) def range_(stop): """:yaql:range Returns an iterator over values from 0 up to stop, not including stop, i.e. [0, stop). :signature: range(stop) :arg stop: right bound for generated list numbers :argType stop: integer :returnType: iterator .. code:: yaql> range(3) [0, 1, 2] """ return iter(six.moves.range(stop)) @specs.parameter('start', int) @specs.parameter('stop', int) @specs.parameter('step', int) def range__(start, stop, step=1): """:yaql:range Returns an iterator over values from start up to stop, not including stop, i.e [start, stop) with step 1 if not specified. :signature: range(start, stop, step => 1) :arg start: left bound for generated list numbers :argType start: integer :arg stop: right bound for generated list numbers :argType stop: integer :arg step: the next element in list is equal to previous + step. 1 is value by default :argType step: integer :returnType: iterator .. code:: yaql> range(1, 4) [1, 2, 3] yaql> range(4, 1, -1) [4, 3, 2] """ return iter(six.moves.range(start, stop, step)) @specs.parameter('start', int) @specs.parameter('step', int) def sequence(start=0, step=1): """:yaql:sequence Returns an iterator to the sequence beginning from start with step. :signature: sequence(start => 0, step => 1) :arg start: start value of the sequence. 0 is value by default :argType start: integer :arg step: the next element is equal to previous + step. 1 is value by default :argType step: integer :returnType: iterator .. code:: yaql> sequence().take(5) [0, 1, 2, 3, 4] """ return itertools.count(start, step) @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('selector', yaqltypes.Lambda()) @specs.inject('operator_gt', yaqltypes.Delegate('#operator_>')) @specs.inject('operator_lt', yaqltypes.Delegate('#operator_<')) @specs.method def order_by(collection, selector, operator_lt, operator_gt): """:yaql:orderBy Returns an iterator over collection elements sorted in ascending order. Selector is applied to each element of the collection to extract sorting key. :signature: collection.orderBy(selector) :receiverArg collection: collection to be ordered :argType collection: iterable :arg selector: specifies a function of one argument that is used to extract a comparison key from each element :argType selector: lambda :returnType: iterator .. code:: yaql> [[1, 'c'], [2, 'b'], [3, 'c'], [0, 'd']].orderBy($[1]) [[2, 'b'], [1, 'c'], [3, 'c'], [0, 'd']] """ oi = OrderingIterable(collection, operator_lt, operator_gt) oi.append_field(selector, True) return oi @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('selector', yaqltypes.Lambda()) @specs.inject('operator_gt', yaqltypes.Delegate('#operator_>')) @specs.inject('operator_lt', yaqltypes.Delegate('#operator_<')) @specs.method def order_by_descending(collection, selector, operator_lt, operator_gt): """:yaql:orderByDescending Returns an iterator over collection elements sorted in descending order. Selector is applied to each element of the collection to extract sorting key. :signature: collection.orderByDescending(selector) :receiverArg collection: collection to be ordered :argType collection: iterable :arg selector: specifies a function of one argument that is used to extract a comparison key from each element :argType selector: lambda :returnType: iterator .. code:: yaql> [4, 2, 3, 1].orderByDescending($) [4, 3, 2, 1] """ oi = OrderingIterable(collection, operator_lt, operator_gt) oi.append_field(selector, False) return oi @specs.parameter('collection', OrderingIterable) @specs.parameter('selector', yaqltypes.Lambda()) @specs.method def then_by(collection, selector, context): """:yaql:thenBy To be used with orderBy or orderByDescending. Uses selector to extract secondary sort key (ascending) from the elements of the collection and adds it to the iterator. :signature: collection.thenBy(selector) :receiverArg collection: collection to be ordered :argType collection: iterable :arg selector: specifies a function of one argument that is used to extract a comparison key from each element :argType selector: lambda :returnType: iterator .. code:: yaql> [[3, 'c'], [2, 'b'], [1, 'c']].orderBy($[1]).thenBy($[0]) [[2, 'b'], [1, 'c'], [3, 'c']] """ collection.append_field(selector, True) collection.context = context return collection @specs.parameter('collection', OrderingIterable) @specs.parameter('selector', yaqltypes.Lambda()) @specs.method def then_by_descending(collection, selector, context): """:yaql:thenByDescending To be used with orderBy or orderByDescending. Uses selector to extract secondary sort key (descending) from the elements of the collection and adds it to the iterator. :signature: collection.thenByDescending(selector) :receiverArg collection: collection to be ordered :argType collection: iterable :arg selector: specifies a function of one argument that is used to extract a comparison key from each element :argType selector: lambda :returnType: iterable .. code:: yaql> [[3,'c'], [2,'b'], [1,'c']].orderBy($[1]).thenByDescending($[0]) [[2, 'b'], [3, 'c'], [1, 'c']] """ collection.append_field(selector, False) collection.context = context return collection @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('key_selector', yaqltypes.Lambda()) @specs.parameter('value_selector', yaqltypes.Lambda()) @specs.parameter('aggregator', yaqltypes.Lambda()) @specs.method def group_by(engine, collection, key_selector, value_selector=None, aggregator=None): """:yaql:groupBy Returns a collection grouped by keySelector with applied valueSelector as values. Returns a list of pairs where the first value is a result value of keySelector and the second is a list of values which have common keySelector return value. :signature: collection.groupBy(keySelector, valueSelector => null, aggregator => null) :receiverArg collection: input collection :argType collection: iterable :arg keySelector: function to be applied to every collection element. Values are grouped by return value of this function :argType keySelector: lambda :arg valueSelector: function to be applied to every collection element to put it under appropriate group. null by default, which means return element itself :argType valueSelector: lambda :arg aggregator: function to aggregate value within each group. null by default, which means no function to be evaluated on groups :argType aggregator: lambda :returnType: list .. code:: yaql> [["a", 1], ["b", 2], ["c", 1], ["d", 2]].groupBy($[1], $[0]) [[1, ["a", "c"]], [2, ["b", "d"]]] yaql> [["a", 1], ["b", 2], ["c", 1]].groupBy($[1], $[0], $.sum()) [[1, "ac"], [2, "b"]] """ groups = {} if aggregator is None: new_aggregator = lambda x: x else: new_aggregator = lambda x: (x[0], aggregator(x[1])) for t in collection: value = t if value_selector is None else value_selector(t) groups.setdefault(key_selector(t), []).append(value) utils.limit_memory_usage(engine, (1, groups)) return select(six.iteritems(groups), new_aggregator) @specs.method @specs.parameter('collections', yaqltypes.Iterable()) def zip_(*collections): """:yaql:zip Returns an iterator over collections, where the n-th iterable contains the n-th element from each of collections. Stops iterating as soon as any of the collections is exhausted. :signature: collection.zip([args]) :receiverArg collection: input collection :argType collection: iterable :arg [args]: collections for zipping with input collection :argType [args]: chain of collections :returnType: iterator .. code:: yaql> [1, 2, 3].zip([4, 5], [6, 7]) [[1, 4, 6], [2, 5, 7]] """ return six.moves.zip(*collections) @specs.method @specs.parameter('collections', yaqltypes.Iterable()) def zip_longest(*collections, **kwargs): """:yaql:zipLongest Returns an iterator over collections, where the n-th iterable contains the n-th element from each of collections. Iterates until all the collections are not exhausted and fills lacking values with default value, which is null by default. :signature: collection.zipLongest([args], default => null) :receiverArg collection: input collection :argType collection: iterable :arg [args]: collections for zipping with input collection :argType [args]: chain of collections :arg default: default value for lacking values, can be passed only as keyword argument. null by default :argType default: any type :returnType: iterator .. code:: yaql> [1, 2, 3].zipLongest([4, 5]) [[1, 4], [2, 5], [3, null]] yaql> [1, 2, 3].zipLongest([4, 5], default => 100) [[1, 4], [2, 5], [3, 100]] """ return six.moves.zip_longest( *collections, fillvalue=kwargs.pop('default', None)) @specs.method @specs.parameter('collection1', yaqltypes.Iterable()) @specs.parameter('collection2', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) @specs.parameter('selector', yaqltypes.Lambda()) def join(engine, collection1, collection2, predicate, selector): """:yaql:join Returns list of selector applied to those combinations of collection1 and collection2 elements, for which predicate is true. :signature: collection1.join(collection2, predicate, selector) :receiverArg collection1: input collection :argType collection1: iterable :arg collection2: other input collection :argType collection2: iterable :arg predicate: function of two arguments to apply to every (collection1, collection2) pair, if returned value is true the pair is passed to selector :argType predicate: lambda :arg selector: function of two arguments to apply to every (collection1, collection2) pair, for which predicate returned true :argType selector: lambda :returnType: iterable .. code:: yaql> [1,2,3,4].join([2,5,6], $1 > $2, [$1, $2]) [[3, 2], [4, 2]] """ collection2 = utils.memorize(collection2, engine) for self_item in collection1: for other_item in collection2: if predicate(self_item, other_item): yield selector(self_item, other_item) @specs.method @specs.parameter('value', nullable=True) @specs.parameter('times', int) def repeat(value, times=-1): """:yaql:repeat Returns collection with value repeated. :signature: value.repeat(times => -1) :receiverArg value: value to be repeated :argType value: any :arg times: how many times repeat value. -1 by default, which means that returned value will be an iterator to the endless sequence of values :argType times: int :returnType: iterable .. code:: yaql> 1.repeat(2) [1, 1] yaql> 1.repeat().take(3) [1, 1, 1] """ if times < 0: return itertools.repeat(value) else: return itertools.repeat(value, times) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def cycle(collection): """:yaql:cycle Makes an iterator returning elements from the collection as if it cycled. :signature: collection.cycle() :receiverArg collection: value to be cycled :argType collection: iterable :returnType: iterator .. code:: yaql> [1, 2].cycle().take(5) [1, 2, 1, 2, 1] """ return itertools.cycle(collection) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) def take_while(collection, predicate): """:yaql:takeWhile Returns elements from the collection as long as the predicate is true. :signature: collection.takeWhile(predicate) :receiverArg collection: input collection :argType collection: iterable :arg predicate: function of one argument to apply to every collection value :argType predicate: lambda :returnType: iterable .. code:: yaql> [1, 2, 3, 4, 5].takeWhile($ < 4) [1, 2, 3] """ return itertools.takewhile(predicate, collection) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) def skip_while(collection, predicate): """:yaql:skipWhile Skips elements from the collection as long as the predicate is true. Then returns an iterator to collection of remaining elements :signature: collection.skipWhile(predicate) :receiverArg collection: input collection :argType collection: iterable :arg predicate: function of one argument to apply to every collection value :argType predicate: lambda :returnType: iterator .. code:: yaql> [1, 2, 3, 4, 5].skipWhile($ < 3) [3, 4, 5] """ return itertools.dropwhile(predicate, collection) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def index_of(collection, item): """:yaql:indexOf Returns the index in the collection of the first item which value is item. -1 is a return value if there is no such item :signature: collection.indexOf(item) :receiverArg collection: input collection :argType collection: iterable :arg item: value to find in collection :argType item: any :returnType: integer .. code:: yaql> [1, 2, 3, 2].indexOf(2) 1 yaql> [1, 2, 3, 2].indexOf(102) -1 """ for i, t in enumerate(collection): if t == item: return i return -1 @specs.method @specs.parameter('collection', yaqltypes.Iterable()) def last_index_of(collection, item): """:yaql:lastIndexOf Returns the index in the collection of the last item which value is item. -1 is a return value if there is no such item :signature: collection.lastIndexOf(item) :receiverArg collection: input collection :argType collection: iterable :arg item: value to find in collection :argType item: any :returnType: integer .. code:: yaql> [1, 2, 3, 2].lastIndexOf(2) 3 """ index = -1 for i, t in enumerate(collection): if t == item: index = i return index @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) def index_where(collection, predicate): """:yaql:indexWhere Returns the index in the collection of the first item which value satisfies the predicate. -1 is a return value if there is no such item :signature: collection.indexWhere(predicate) :receiverArg collection: input collection :argType collection: iterable :arg predicate: function of one argument to apply on every value :argType predicate: lambda :returnType: integer .. code:: yaql> [1, 2, 3, 2].indexWhere($ > 2) 2 yaql> [1, 2, 3, 2].indexWhere($ > 3) -1 """ for i, t in enumerate(collection): if predicate(t): return i return -1 @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) def last_index_where(collection, predicate): """:yaql:lastIndexWhere Returns the index in the collection of the last item which value satisfies the predicate. -1 is a return value if there is no such item :signature: collection.lastIndexWhere(predicate) :receiverArg collection: input collection :argType collection: iterable :arg predicate: function of one argument to apply on every value :argType predicate: lambda :returnType: integer .. code:: yaql> [1, 2, 3, 2].lastIndexWhere($ = 2) 3 """ index = -1 for i, t in enumerate(collection): if predicate(t): index = i return index @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('length', int) @specs.inject('to_list', yaqltypes.Delegate('to_list', method=True)) def slice_(collection, length, to_list): """:yaql:slice Returns collection divided into list of collections with max size of new parts equal to length. :signature: collection.slice(length) :receiverArg collection: input collection :argType collection: iterable :arg length: max length of new collections :argType length: integer :returnType: list .. code:: yaql> range(1,6).slice(2) [[1, 2], [3, 4], [5]] """ collection = iter(collection) while True: res = to_list(itertools.islice(collection, length)) if res: yield res else: break @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) @specs.inject('to_list', yaqltypes.Delegate('to_list', method=True)) def split_where(collection, predicate, to_list): """:yaql:splitWhere Returns collection divided into list of collections where delimiters are values for which predicate returns true. Delimiters are deleted from result. :signature: collection.splitWhere(predicate) :receiverArg collection: input collection :argType collection: iterable :arg predicate: function of one argument to be applied on every element. Elements for which predicate returns true are delimiters for new list :argType predicate: lambda :returnType: list .. code:: yaql> [1, 2, 3, 4, 5, 6, 7].splitWhere($ mod 3 = 0) [[1, 2], [4, 5], [7]] """ lst = to_list(collection) start = 0 end = 0 while end < len(lst): if predicate(lst[end]): yield lst[start:end] start = end + 1 end += 1 if start != end: yield lst[start:end] @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('predicate', yaqltypes.Lambda()) @specs.inject('to_list', yaqltypes.Delegate('to_list', method=True)) def slice_where(collection, predicate, to_list): """:yaql:sliceWhere Splits collection into lists. Within every list predicate evaluated on its items returns the same value while predicate evaluated on the items of the adjacent lists returns different values. Returns an iterator to lists. :signature: collection.sliceWhere(predicate) :receiverArg collection: input collection :argType collection: iterable :arg predicate: function of one argument to be applied on every element. Elements for which predicate returns true are delimiters for new list and are present in new collection as separate collections :argType predicate: lambda :returnType: iterator .. code:: yaql> [1, 2, 3, 4, 5, 6, 7].sliceWhere($ mod 3 = 0) [[1, 2], [3], [4, 5], [6], [7]] """ lst = to_list(collection) start = 0 end = 0 p1 = utils.NO_VALUE while end < len(lst): p2 = predicate(lst[end]) if p2 != p1 and p1 is not utils.NO_VALUE: yield lst[start:end] start = end end += 1 p1 = p2 if start != end: yield lst[start:end] @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('index', int) @specs.inject('to_list', yaqltypes.Delegate('to_list', method=True)) def split_at(collection, index, to_list): """:yaql:splitAt Splits collection into two lists by index. :signature: collection.splitAt(index) :receiverArg collection: input collection :argType collection: iterable :arg index: the index of collection to be delimiter for splitting :argType index: integer :returnType: list .. code:: yaql> [1, 2, 3, 4].splitAt(1) [[1], [2, 3, 4]] yaql> [1, 2, 3, 4].splitAt(0) [[], [1, 2, 3, 4]] """ lst = to_list(collection) return [lst[:index], lst[index:]] @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('selector', yaqltypes.Lambda()) def aggregate(collection, selector, seed=utils.NO_VALUE): """:yaql:aggregate Applies selector of two arguments cumulatively: to the first two elements of collection, then to the result of the previous selector applying and to the third element, and so on. Returns the result of last selector applying. :signature: collection.aggregate(selector, seed => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg selector: function of two arguments to be applied on every next pair of collection :argType selector: lambda :arg seed: if specified, it is used as start value for accumulating and becomes a default when the collection is empty. NoValue by default :argType seed: collection elements type :returnType: collection elements type .. code:: yaql> [a,a,b,a,a].aggregate($1 + $2) "aabaa" yaql> [].aggregate($1 + $2, 1) 1 """ if seed is utils.NO_VALUE: return six.moves.reduce(selector, collection) else: return six.moves.reduce(selector, collection, seed) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.inject('to_list', yaqltypes.Delegate('to_list', method=True)) def reverse(collection, to_list): """:yaql:reverse Returns reversed collection, evaluated to list. :signature: collection.reverse() :receiverArg collection: input collection :argType collection: iterable :returnType: list .. code:: yaql> [1, 2, 3, 4].reverse() [4, 3, 2, 1] """ return reversed(to_list(collection)) def _merge_dicts(dict1, dict2, list_merge_func, item_merger, max_levels=0): result = {} for key, value1 in six.iteritems(dict1): result[key] = value1 if key in dict2: value2 = dict2[key] if max_levels != 1 and isinstance(value2, utils.MappingType): if not isinstance(value1, utils.MappingType): raise TypeError( 'Cannot merge {0} with {1}'.format( type(value1), type(value2))) result[key] = _merge_dicts( value1, value2, list_merge_func, item_merger, 0 if max_levels == 0 else max_levels - 1) elif max_levels != 1 and utils.is_sequence(value2): if not utils.is_sequence(value1): raise TypeError( 'Cannot merge {0} with {1}'.format( type(value1), type(value2))) result[key] = list_merge_func(value1, value2) else: result[key] = item_merger(value1, value2) for key2, value2 in six.iteritems(dict2): if key2 not in result: result[key2] = value2 return result @specs.method @specs.parameter('d', utils.MappingType, alias='dict') @specs.parameter('another', utils.MappingType) @specs.parameter('list_merger', yaqltypes.Lambda()) @specs.parameter('item_merger', yaqltypes.Lambda()) @specs.parameter('max_levels', int) @specs.inject('to_list', yaqltypes.Delegate('to_list', method=True)) def merge_with(engine, to_list, d, another, list_merger=None, item_merger=None, max_levels=0): """:yaql:mergeWith Performs a deep merge of two dictionaries. :signature: dict.mergeWith(another, listMerger => null, itemMerger => null, maxLevels => null) :receiverArg dict: input dictionary :argType dict: mapping :arg another: dictionary to merge with :argType another: mapping :arg listMerger: function to be applied while merging two lists. null is a default which means listMerger to be distinct(lst1 + lst2) :argType listMerger: lambda :arg itemMerger: function to be applied while merging two items. null is a default, which means itemMerger to be a second item for every pair. :argType itemMerger: lambda :arg maxLevels: number which describes how deeply merge dicts. 0 by default, which means going throughout them :argType maxLevels: int :returnType: mapping .. code:: yaql> {'a'=> 1, 'b'=> 2, 'c'=> [1, 2]}.mergeWith({'d'=> 5, 'b'=> 3, 'c'=> [2, 3]}) {"a": 1, "c": [1, 2, 3], "b": 3, "d": 5} yaql> {'a'=> 1, 'b'=> 2, 'c'=> [1, 2]}.mergeWith({'d'=> 5, 'b'=> 3, 'c'=> [2, 3]}, $1+$2) {"a": 1, "c": [1, 2, 2, 3], "b": 3, "d": 5} yaql> {'a'=> 1, 'b'=> 2, 'c'=> [1, 2]}.mergeWith({'d'=> 5, 'b'=> 3, 'c'=> [2, 3]}, $1+$2, $1) {"a": 1, "c": [1, 2, 2, 3], "b": 2, "d": 5} yaql> {'a'=> 1, 'b'=> 2, 'c'=> [1, 2]}.mergeWith({'d'=> 5, 'b'=> 3, 'c'=> [2, 3]}, maxLevels => 1) {"a": 1, "c": [2, 3], "b": 3, "d": 5} """ if list_merger is None: list_merger = lambda lst1, lst2: to_list( distinct(engine, lst1 + lst2)) if item_merger is None: item_merger = lambda x, y: y return _merge_dicts(d, another, list_merger, item_merger, max_levels) def is_iterable(value): """:yaql:isIterable Returns true if value is iterable, false otherwise. :signature: isIterable(value) :arg value: value to be checked :argType value: any :returnType: boolean .. code:: yaql> isIterable([]) true yaql> isIterable(set(1,2)) true yaql> isIterable("foo") false yaql> isIterable({"a" => 1}) false """ return utils.is_iterable(value) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('selector', yaqltypes.Lambda()) def accumulate(collection, selector, seed=utils.NO_VALUE): """:yaql:accumulate Applies selector of two arguments cumulatively to the items of collection from begin to end, so as to accumulate the collection to a list of intermediate values. :signature: collection.accumulate(selector, seed => NoValue) :receiverArg collection: input collection :argType collection: iterable :arg selector: function of two arguments to be applied on every next pair of collection :argType selector: lambda :arg seed: value to use as the first for accumulating. noValue by default :argType seed: collection elements type :returnType: list .. code:: yaql> [1, 2, 3].accumulate($1+$2) [1, 3, 6] yaql> [1, 2, 3].accumulate($1+$2, 100) [100, 101, 103, 106] yaql> [].accumulate($1+$2,1) [1] """ it = iter(collection) if seed is utils.NO_VALUE: try: seed = next(it) except StopIteration: raise TypeError( 'accumulate() of empty sequence with no initial value') yield seed total = seed for x in it: total = selector(total, x) yield total @specs.parameter('predicate', yaqltypes.Lambda()) @specs.parameter('producer', yaqltypes.Lambda()) @specs.parameter('selector', yaqltypes.Lambda()) @specs.parameter('decycle', bool) def generate(engine, initial, predicate, producer, selector=None, decycle=False): """:yaql:generate Returns iterator to values beginning from initial value with every next value produced with producer applied to every previous value, while predicate is true. Represents traversal over the list where each next element is obtained by the lambda result from the previous element. :signature: generate(initial, predicate, producer, selector => null, decycle => false) :arg initial: value to start from :argType initial: any type :arg predicate: function of one argument to be applied on every new value. Stops generating if return value is false :argType predicate: lambda :arg producer: function of one argument to produce the next value :argType producer: lambda :arg selector: function of one argument to store every element in the resulted list. none by default which means to store producer result :argType selector: lambda :arg decycle: return only distinct values if true, false by default :argType decycle: boolean :returnType: list .. code:: yaql> generate(0, $ < 10, $ + 2) [0, 2, 4, 6, 8] yaql> generate(1, $ < 10, $ + 2, $ * 1000) [1000, 3000, 5000, 7000, 9000] """ past_items = None if not decycle else set() while predicate(initial): if past_items is not None: if initial in past_items: break past_items.add(initial) utils.limit_memory_usage(engine, (1, past_items)) if selector is None: yield initial else: yield selector(initial) initial = producer(initial) @specs.parameter('producer', yaqltypes.Lambda()) @specs.parameter('selector', yaqltypes.Lambda()) @specs.parameter('decycle', bool) @specs.parameter('depth_first', bool) def generate_many(engine, initial, producer, selector=None, decycle=False, depth_first=False): """:yaql:generateMany Returns iterator to values beginning from initial queue of values with every next value produced with producer applied to top of queue, while predicate is true. Represents tree traversal, where producer is used to get child nodes. :signature: generateMany(initial, producer, selector => null, decycle => false, depthFirst => false) :arg initial: value to start from :argType initial: any type :arg producer: function to produce the next value for queue :argType producer: lambda :arg selector: function of one argument to store every element in the resulted list. none by default which means to store producer result :argType selector: lambda :arg decycle: return only distinct values if true, false by default :argType decycle: boolean :arg depthFirst: if true puts produced elements to the start of queue, false by default :argType depthFirst: boolean :returnType: list .. code:: yaql> generateMany("1", {"1" => ["2", "3"], "2"=>["4"], "3"=>["5"] }.get($, [])) ["1", "2", "3", "4", "5"] """ past_items = None if not decycle else set() queue = utils.QueueType([initial]) while queue: item = queue.popleft() if past_items is not None: if item in past_items: continue else: past_items.add(item) utils.limit_memory_usage(engine, (1, past_items)) if selector is None: yield item else: yield selector(item) produced = producer(item) if depth_first: len_before = len(queue) queue.extend(produced) queue.rotate(len(queue) - len_before) else: queue.extend(produced) utils.limit_memory_usage(engine, (1, queue)) @specs.method @specs.parameter('collection', yaqltypes.Iterable()) @specs.parameter('default', yaqltypes.Iterable()) def default_if_empty(engine, collection, default): """:yaql:defaultIfEmpty Returns default value if collection is empty. :signature: collection.defaultIfEmpty(default) :receiverArg collection: input collection :argType collection: iterable :arg default: value to be returned if collection size is 0 :argType default: iterable :returnType: iterable .. code:: yaql> [].defaultIfEmpty([1, 2]) [1, 2] """ if isinstance(collection, (utils.SequenceType, utils.SetType)): return default if len(collection) == 0 else collection collection = memorize(collection, engine) it = iter(collection) try: next(it) return collection except StopIteration: return default def register(context): context.register_function(where) context.register_function(where, name='filter') context.register_function(select) context.register_function(select, name='map') context.register_function(collection_attribution) context.register_function(limit) context.register_function(limit, name='take') context.register_function(skip) context.register_function(append) context.register_function(distinct) context.register_function(enumerate_) context.register_function(any_) context.register_function(all_) context.register_function(concat) context.register_function(count_) context.register_function(count) context.register_function(memorize) context.register_function(sum_) context.register_function(min_) context.register_function(max_) context.register_function(first) context.register_function(single) context.register_function(last) context.register_function(select_many) context.register_function(range_) context.register_function(range__) context.register_function(order_by) context.register_function(order_by_descending) context.register_function(then_by) context.register_function(then_by_descending) context.register_function(group_by) context.register_function(join) context.register_function(zip_) context.register_function(zip_longest) context.register_function(repeat) context.register_function(cycle) context.register_function(take_while) context.register_function(skip_while) context.register_function(index_of) context.register_function(last_index_of) context.register_function(index_where) context.register_function(last_index_where) context.register_function(slice_) context.register_function(split_where) context.register_function(slice_where) context.register_function(split_at) context.register_function(aggregate) context.register_function(aggregate, name='reduce') context.register_function(accumulate) context.register_function(reverse) context.register_function(merge_with) context.register_function(is_iterable) context.register_function(sequence) context.register_function(generate) context.register_function(generate_many) context.register_function(default_if_empty) yaql-1.1.3/yaql/standard_library/regex.py000066400000000000000000000421331305545650400204520ustar00rootroot00000000000000# 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. """ The module contains functions for regular expressions. """ import re import six from yaql.language import specs from yaql.language import yaqltypes REGEX_TYPE = type(re.compile('.')) @specs.parameter('pattern', yaqltypes.String()) def regex(pattern, ignore_case=False, multi_line=False, dot_all=False): """:yaql:regex Returns regular expression object with provided flags. Can be used for matching using matches method. :signature: regex(pattern,ignoreCase => false, multiLine => false, dotAll => false) :arg pattern: regular expression pattern to be compiled to regex object :argType pattern: string :arg ignoreCase: true makes performing case-insensitive matching. :argType ignoreCase: boolean :arg multiLine: true makes character '^' to match at the beginning of the string and at the beginning of each line, the character '$' to match at the end of the string and at the end of each line. false means '^' to match only at the beginning of the string, '$' only at the end of the string. :argType multiLine: boolean :arg dotAll: true makes the '.' special character to match any character (including a newline). false makes '.' to match anything except a newline. :argType dotAll: boolean :returnType: regex object .. code:: yaql> regex("a.c").matches("abc") true yaql> regex("A.c").matches("abc") false yaql> regex("A.c", ignoreCase => true).matches("abc") true """ flags = re.UNICODE if ignore_case: flags |= re.IGNORECASE if multi_line: flags |= re.MULTILINE if dot_all: flags |= re.DOTALL return re.compile(pattern, flags) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.method def matches(regexp, string): """:yaql:matches Returns true if string matches regexp. :signature: regexp.matches(string) :receiverArg regexp: regex pattern :argType regexp: regex object :arg string: string to find match in :argType string: string :returnType: boolean .. code:: yaql> regex("a.c").matches("abc") true """ return regexp.search(string) is not None @specs.parameter('string', yaqltypes.String()) @specs.parameter('regexp', yaqltypes.String()) @specs.method def matches_(string, regexp): """:yaql:matches Returns true if string matches regexp, false otherwise. :signature: string.matches(regexp) :receiverArg string: string to find match in :argType string: string :arg regexp: regex pattern :argType regexp: regex object :returnType: boolean .. code:: yaql> "abc".matches("a.c") true """ return re.search(regexp, string) is not None @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.name('#operator_=~') def matches_operator_regex(string, regexp): """:yaql:operator =~ Returns true if left matches right, false otherwise. :signature: left =~ right :arg left: string to find match in :argType left: string :arg right: regex pattern :argType right: regex :returnType: boolean .. code:: yaql> "abc" =~ regex("a.c") true """ return regexp.search(string) is not None @specs.parameter('pattern', yaqltypes.String()) @specs.parameter('string', yaqltypes.String()) @specs.name('#operator_=~') def matches_operator_string(string, pattern): """:yaql:operator =~ Returns true if left matches right, false otherwise. :signature: left =~ right :arg left: string to find match in :argType left: string :arg right: regex pattern :argType right: string :returnType: boolean .. code:: yaql> "abc" =~ "a.c" true """ return re.search(pattern, string) is not None @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.name('#operator_!~') def not_matches_operator_regex(string, regexp): """:yaql:operator !~ Returns true if left doesn't match right, false otherwise. :signature: left !~ right :arg left: string to find match in :argType left: string :arg right: regex pattern :argType right: regex :returnType: boolean .. code:: yaql> "acb" !~ regex("a.c") true yaql> "abc" !~ regex("a.c") false """ return regexp.search(string) is None @specs.parameter('pattern', yaqltypes.String()) @specs.parameter('string', yaqltypes.String()) @specs.name('#operator_!~') def not_matches_operator_string(string, pattern): """:yaql:operator !~ Returns true if left doesn't match right, false otherwise. :signature: left !~ right :arg left: string to find match in :argType left: string :arg right: regex pattern :argType right: regex object :returnType: boolean .. code:: yaql> "acb" !~ regex("a.c") true yaql> "abc" !~ regex("a.c") false """ return re.search(pattern, string) is None def _publish_match(context, match): rec = { 'value': match.group(), 'start': match.start(0), 'end': match.end(0) } context['$1'] = rec for i, t in enumerate(match.groups(), 1): rec = { 'value': t, 'start': match.start(i), 'end': match.end(i) } context['$' + str(i + 1)] = rec for key, value, in six.itervalues(match.groupdict()): rec = { 'value': value, 'start': match.start(value), 'end': match.end(value) } context['$' + key] = rec @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('selector', yaqltypes.Lambda(with_context=True)) @specs.method def search(context, regexp, string, selector=None): """:yaql:search Search substring which matches regexp. Returns selector applied to dictionary {"start" => ..., "end" => ..., "value" => ...} where appropriate values describe start of substring, its end and itself. By default, if no selector is specified, returns only substring. null is a return value if there is no substring which matches regexp. :signature: regexp.search(string, selector => null) :receiverArg regexp: regex pattern :argType regexp: regex object :arg string: string to find match in :argType string: string :arg selector: lambda function to be applied to resulted dictionary with keys 'start', 'end', 'value'. null by default, which means to return only substring. :argType selector: lambda :returnType: string or selector return type .. code:: yaql> regex("a.c").search("abcabc") "abc" yaql> regex("a.c").search("cabc", $) { "start": 1, "end": 4, "value": "abc" } yaql> regex("a.c").search("cabc", $.start) 1 """ res = regexp.search(string) new_context = context.create_child_context() if res is None: return None if selector is None: return res.group() _publish_match(new_context, res) return selector(new_context) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('selector', yaqltypes.Lambda(with_context=True)) @specs.method def search_all(context, regexp, string, selector=None): """:yaql:searchAll Search all substrings which matches regexp. Returns list of applied to dictionary {"start" => ..., "end" => ..., "value" => ...} selector, where appropriate values describe start of every substring, its end and itself. By default, if no selector is specified, returns only list of substrings. :signature: regexp.searchAll(string, selector => null) :receiverArg regexp: regex pattern :argType regexp: regex object :arg string: string to find match in :argType string: string :arg selector: lambda function to be applied to resulted dictionary of every substring with keys 'start', 'end', 'value'. null by default, which means to return only list of substrings. :argType selector: lambda :returnType: list .. code:: yaql> regex("a.c").searchAll("abcadc") ["abc", "adc"] yaql> regex("a.c").searchAll("abcadc", $) [ { "start": 0, "end": 3, "value": "abc" }, { "start": 3, "end": 6, "value": "adc" } ] :name: searchAll """ for res in regexp.finditer(string): new_context = context.create_child_context() if selector is None: yield res.group() else: _publish_match(new_context, res) yield selector(new_context) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('max_split', int) @specs.method def split(regexp, string, max_split=0): """:yaql:split Splits string by regexp matches and returns list of strings. :signature: regexp.split(string, maxSplit => 0) :receiverArg regexp: regex pattern :argType regexp: regex object :arg string: string to be splitted :argType string: string :arg maxSplit: how many first splits to do. 0 by default, which means to split by all matches :argType maxSplit: integer :returnType: list .. code:: yaql> regex("a.").split("abcadc") ["", "c", "c"] yaql> regex("a.").split("abcadc", maxSplit => 1) ["", "cadc"] """ return regexp.split(string, max_split) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('max_split', int) @specs.method @specs.name('split') def split_string(string, regexp, max_split=0): """:yaql:split Splits string by regexp matches and returns list of strings. :signature: string.split(regexp, maxSplit => 0) :receiverArg string: string to be splitted :argType string: string :arg regexp: regex pattern :argType regexp: regex object :arg maxSplit: how many first splits to do. 0 by default, which means to split by all matches :argType maxSplit: integer :returnType: list .. code:: yaql> "abcadc".split(regex("a.")) ["", "c", "c"] yaql> "abcadc".split(regex("a."), maxSplit => 1) ["", "cadc"] """ return regexp.split(string, max_split) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('repl', yaqltypes.String()) @specs.parameter('count', int) @specs.method def replace(regexp, string, repl, count=0): """:yaql:replace Returns the string obtained by replacing the leftmost non-overlapping matches of regexp in string by the replacement repl, where the latter is only string-type. :signature: regexp.replace(string, repl, count => 0) :receiverArg regexp: regex pattern :argType regexp: regex object :arg string: string to make replace in :argType string: string :arg repl: string to replace matches of regexp :argType repl: string :arg count: how many first replaces to do. 0 by default, which means to do all replacements :argType count: integer :returnType: string .. code:: yaql> regex("a.").replace("abcadc", "xx") "xxcxxc" yaql> regex("a.").replace("abcadc", "xx", count => 1) "xxcadc" """ return regexp.sub(repl, string, count) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('repl', yaqltypes.String()) @specs.parameter('count', int) @specs.method @specs.name('replace') def replace_string(string, regexp, repl, count=0): """:yaql:replace Returns the string obtained by replacing the leftmost non-overlapping matches of regexp in string by the replacement repl, where the latter is only string-type. :signature: string.replace(regexp, repl, count => 0) :receiverArg string: string to make replace in :argType string: string :arg regexp: regex pattern :argType regexp: regex object :arg repl: string to replace matches of regexp :argType repl: string :arg count: how many first replaces to do. 0 by default, which means to do all replacements :argType count: integer :returnType: string .. code:: yaql> "abcadc".replace(regex("a."), "xx") "xxcxxc" """ return replace(regexp, string, repl, count) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('repl', yaqltypes.Lambda(with_context=True)) @specs.parameter('count', int) @specs.method def replace_by(context, regexp, string, repl, count=0): """:yaql:replaceBy Returns the string obtained by replacing the leftmost non-overlapping matches of regexp in string by repl, where the latter is an expression to get replacements by obtained matches. :signature: regexp.replaceBy(string, repl, count => 0) :receiverArg regexp: regex pattern :argType regexp: regex object :arg string: string to make replace in :argType string: string :arg repl: lambda function which returns string to make replacements according to input matches :argType repl: lambda :arg count: how many first replaces to do. 0 by default, which means to do all replacements :argType count: integer :returnType: string .. code:: yaql> regex("a.c").replaceBy("abcadc", switch($.value = "abc" => xx, $.value = "adc" => yy)) "xxyy" """ def repl_func(match): new_context = context.create_child_context() _publish_match(context, match) return repl(new_context) return regexp.sub(repl_func, string, count) @specs.parameter('regexp', REGEX_TYPE) @specs.parameter('string', yaqltypes.String()) @specs.parameter('repl', yaqltypes.Lambda(with_context=True)) @specs.parameter('count', int) @specs.method @specs.name('replaceBy') def replace_by_string(context, string, regexp, repl, count=0): """:yaql:replaceBy Replaces matches of regexp in string with values provided by the supplied function. :signature: string.replaceBy(regexp, repl, count => 0) :receiverArg string: string to make replace in :argType string: string :arg regexp: regex pattern :argType regexp: regex object :arg repl: lambda function which returns string to make replacements according to input matches :argType repl: lambda :arg count: how many first replaces to do. 0 by default, which means to do all replacements :argType count: integer :returnType: string .. code:: yaql> "abcadc".replaceBy(regex("a.c"), switch($.value = "abc" => xx, $.value = "adc" => yy)) "xxyy" """ return replace_by(context, regexp, string, repl, count) @specs.parameter('string', yaqltypes.String()) def escape_regex(string): """:yaql:escapeRegex Returns string with all the characters except ASCII letters, numbers, and '_' escaped. :signature: escapeRegex(string) :arg string: string to backslash all non-alphanumerics :argType string: string :returnType: string .. code:: yaql> escapeRegex('a.') "a\\." """ return re.escape(string) def is_regex(value): """:yaql:isRegex Returns true if value is a regex object. :signature: isRegex(value) :arg value: string to backslash all non-alphanumerics :argType value: any :returnType: boolean .. code:: yaql> isRegex(regex("a.c")) true yaql> isRegex(regex("a.c").matches("abc")) false """ return isinstance(value, REGEX_TYPE) def register(context): context.register_function(regex) context.register_function(matches) context.register_function(matches_) context.register_function(matches_operator_string) context.register_function(matches_operator_regex) context.register_function(not_matches_operator_string) context.register_function(not_matches_operator_regex) context.register_function(search) context.register_function(search_all) context.register_function(split) context.register_function(split_string) context.register_function(replace) context.register_function(replace_by) context.register_function(replace_string) context.register_function(replace_by_string) context.register_function(escape_regex) context.register_function(is_regex) yaql-1.1.3/yaql/standard_library/strings.py000066400000000000000000000722771305545650400210450ustar00rootroot00000000000000# 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. """ The module describes which operations can be done with strings in YAQL. """ import string as string_module import six from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes @specs.parameter('left', yaqltypes.String()) @specs.parameter('right', yaqltypes.String()) @specs.name('#operator_>') def gt(left, right): """:yaql:operator > Returns true if the left operand is strictly greater than the right, ordering lexicographically, otherwise false. :signature: left > right :arg left: left operand :argType left: string :arg right: right operand :argType right: string :returnType: boolean .. code:: yaql> "abc" > "ab" true yaql> "abc" > "abb" true yaql> "abc" > "abc" false """ return left > right @specs.parameter('left', yaqltypes.String()) @specs.parameter('right', yaqltypes.String()) @specs.name('#operator_<') def lt(left, right): """:yaql:operator < Returns true if the left operand is strictly less than the right, ordering lexicographically, otherwise false. :signature: left < right :arg left: left operand :argType left: string :arg right: right operand :argType right: string :returnType: boolean .. code:: yaql> "ab" < "abc" true yaql> "abb" < "abc" true yaql> "abc" < "abc" false """ return left < right @specs.parameter('left', yaqltypes.String()) @specs.parameter('right', yaqltypes.String()) @specs.name('#operator_>=') def gte(left, right): """:yaql:operator >= Returns true if the left operand is greater or equal to the right, ordering lexicographically, otherwise false. :signature: left >= right :arg left: left operand :argType left: string :arg right: right operand :argType right: string :returnType: boolean .. code:: yaql> "abc" >= "ab" true yaql> "abc" >= "abc" true """ return left >= right @specs.parameter('left', yaqltypes.String()) @specs.parameter('right', yaqltypes.String()) @specs.name('#operator_<=') def lte(left, right): """:yaql:operator <= Returns true if the left operand is less or equal to the right, ordering lexicographically, otherwise false. :signature: left <= right :arg left: left operand :argType left: string :arg right: right operand :argType right: string :returnType: boolean .. code:: yaql> "ab" <= "abc" true yaql> "abc" <= "abc" true """ return left <= right @specs.parameter('args', yaqltypes.String()) def concat(*args): """:yaql:concat Returns concatenated args. :signature: concat([args]) :arg [args]: values to be joined :argType [args]: string :returnType: string .. code:: yaql> concat("abc", "de", "f") "abcdef" """ return ''.join(args) @specs.parameter('string', yaqltypes.String()) @specs.method def to_upper(string): """:yaql:toUpper Returns a string with all case-based characters uppercase. :signature: string.toUpper() :receiverArg string: value to uppercase :argType string: string :returnType: string .. code:: yaql> "aB1c".toUpper() "AB1C" """ return string.upper() @specs.parameter('string', yaqltypes.String()) @specs.extension_method def len_(string): """:yaql:len Returns size of the string. :signature: string.len() :receiverArg string: input string :argType string: string :returnType: integer .. code:: yaql> "abc".len() 3 """ return len(string) @specs.parameter('string', yaqltypes.String()) @specs.method def to_lower(string): """:yaql:toLower Returns a string with all case-based characters lowercase. :signature: string.toLower() :receiverArg string: value to lowercase :argType string: string :returnType: string .. code:: yaql> "AB1c".toLower() "ab1c" """ return string.lower() @specs.parameter('string', yaqltypes.String()) @specs.parameter('separator', yaqltypes.String(nullable=True)) @specs.parameter('max_splits', int) @specs.method def split(string, separator=None, max_splits=-1): """:yaql:split Returns a list of tokens in the string, using separator as the delimiter. :signature: string.split(separator => null, maxSplits => -1) :receiverArg string: value to be splitted :argType string: string :arg separator: delimiter for splitting. null by default, which means splitting with whitespace characters :argType separator: string :arg maxSplits: maximum number of splittings. -1 by default, which means all possible splits are done :argType maxSplits: integer :returnType: list .. code:: yaql> "abc de f".split() ["abc", "de", "f"] yaql> "abc de f".split(maxSplits => 1) ["abc", "de f"] yaql> "abcde".split("c") ["ab", "de"] """ return string.split(separator, max_splits) @specs.parameter('string', yaqltypes.String()) @specs.parameter('separator', yaqltypes.String(nullable=True)) @specs.parameter('max_splits', int) @specs.method def right_split(string, separator=None, max_splits=-1): """:yaql:rightSplit Returns a list of tokens in the string, using separator as the delimiter. If maxSplits is given then at most maxSplits splits are done - the rightmost ones. :signature: string.rightSplit(separator => null, maxSplits => -1) :receiverArg string: value to be splitted :argType string: string :arg separator: delimiter for splitting. null by default, which means splitting with whitespace characters :argType separator: string :arg maxSplits: number of splits to be done - the rightmost ones. -1 by default, which means all possible splits are done :argType maxSplits: integer :returnType: list .. code:: yaql> "abc de f".rightSplit() ["abc", "de", "f"] yaql> "abc de f".rightSplit(maxSplits => 1) ["abc de", "f"] """ return string.rsplit(separator, max_splits) @specs.parameter('sequence', yaqltypes.Iterable()) @specs.parameter('separator', yaqltypes.String()) @specs.inject('str_delegate', yaqltypes.Delegate('str')) @specs.method def join(sequence, separator, str_delegate): """:yaql:join Returns a string with sequence elements joined by the separator. :signature: sequence.join(separator) :receiverArg sequence: chain of values to be joined :argType sequence: sequence of strings :arg separator: value to be placed between joined pairs :argType separator: string :returnType: string .. code:: yaql> ["abc", "de", "f"].join("") "abcdef" yaql> ["abc", "de", "f"].join("|") "abc|de|f" """ return separator.join(six.moves.map(str_delegate, sequence)) @specs.parameter('sequence', yaqltypes.Iterable()) @specs.parameter('separator', yaqltypes.String()) @specs.inject('str_delegate', yaqltypes.Delegate('str')) @specs.method def join_(separator, sequence, str_delegate): """:yaql:join Returns a string with sequence elements joined by the separator. :signature: separator.join(sequence) :receiverArg separator: value to be placed between joined pairs :argType separator: string :arg sequence: chain of values to be joined :argType sequence: sequence of strings :returnType: string .. code:: yaql> "|".join(["abc", "de", "f"]) "abc|de|f" """ return join(sequence, separator, str_delegate) @specs.parameter('value', nullable=True) def str_(value): """:yaql:str Returns a string representation of the value. :signature: str(value) :arg value: value to be evaluated to string :argType value: any :returnType: string .. code:: yaql> str(["abc", "de"]) "(u'abc', u'd')" yaql> str(123) "123" """ if value is None: return 'null' elif value is True: return 'true' elif value is False: return 'false' else: return six.text_type(value) @specs.parameter('string', yaqltypes.String()) @specs.parameter('chars', yaqltypes.String(nullable=True)) @specs.method def trim(string, chars=None): """:yaql:trim Returns a string with the leading and trailing chars removed. :signature: string.trim(chars => null) :receiverArg string: value to be trimmed :argType string: string :arg chars: symbols to be removed from input string. null by default, which means trim is done with whitespace characters :argType chars: string :returnType: string .. code:: yaql> " abcd ".trim() "abcd" yaql> "aababa".trim("a") "bab" """ return string.strip(chars) @specs.parameter('string', yaqltypes.String()) @specs.parameter('chars', yaqltypes.String(nullable=True)) @specs.method def trim_left(string, chars=None): """:yaql:trimLeft Returns a string with the leading chars removed. :signature: string.trimLeft(chars => null) :receiverArg string: value to be trimmed :argType string: string :arg chars: symbols to be removed from start of input string. null by default, which means trim is done with whitespace characters :argType chars: string :returnType: string .. code:: yaql> " abcd ".trimLeft() "abcd " yaql> "aababa".trimLeft("a") "baba" """ return string.lstrip(chars) @specs.parameter('string', yaqltypes.String()) @specs.parameter('chars', yaqltypes.String(nullable=True)) @specs.method def trim_right(string, chars=None): """:yaql:trimRight Returns a string with the trailing chars removed. :signature: string.trimRight(chars => null) :receiverArg string: value to be trimmed :argType string: string :arg chars: symbols to be removed from end of input string. null by default, which means trim is done with whitespace characters :argType chars: string :returnType: string .. code:: yaql> " abcd ".trimRight() " abcd" yaql> "aababa".trimRight("a") "aabab" """ return string.rstrip(chars) @specs.parameter('string', yaqltypes.String(nullable=True)) @specs.parameter('chars', yaqltypes.String(nullable=True)) @specs.extension_method def norm(string, chars=None): """:yaql:norm Returns a string with the leading and trailing chars removed. If the resulting string is empty, returns null. :signature: string.norm(chars => null) :receiverArg string: value to be cut with specified chars :argType string: string :arg chars: symbols to be removed from the start and the end of input string. null by default, which means norm is done with whitespace characters :argType chars: string :returnType: string .. code:: yaql> " abcd ".norm() "abcd" yaql> "aaaa".norm("a") null """ if string is None: return None value = string.strip(chars) return None if not value else value @specs.parameter('string', yaqltypes.String(nullable=True)) @specs.parameter('trim_spaces', bool, alias='trim') @specs.parameter('chars', yaqltypes.String(nullable=True)) @specs.extension_method def is_empty(string, trim_spaces=True, chars=None): """:yaql:isEmpty Returns true if the string with removed leading and trailing chars is empty. :signature: string.isEmpty(trimSpaces => true, chars => null) :receiverArg string: value to be checked for emptiness after trim :argType string: string :arg trimSpaces: true by default, which means string to be trimmed with chars. false means checking whether input string is empty :argType trimSpaces: boolean :arg chars: symbols for trimming. null by default, which means trim is done with whitespace characters :argType chars: string :returnType: boolean .. code:: yaql> "abaab".isEmpty(chars=>"ab") true yaql> "aba".isEmpty(chars=>"a") false """ if string is None: return True if trim_spaces: string = string.strip(chars) return not string @specs.parameter('string', yaqltypes.String()) @specs.parameter('old', yaqltypes.String()) @specs.parameter('new', yaqltypes.String()) @specs.parameter('count', int) @specs.method def replace(string, old, new, count=-1): """:yaql:replace Returns a string with first count occurrences of old replaced with new. :signature: string.replace(old, new, count => -1) :receiverArg string: input string :argType string: string :arg old: value to be replaced :argType old: string :arg new: replacement for old value :argType new: string :arg count: how many first replacements to do. -1 by default, which means to do all replacements :argType count: integer :returnType: string .. code:: yaql> "abaab".replace("ab", "cd") "cdacd" """ return string.replace(old, new, count) @specs.parameter('string', yaqltypes.String()) @specs.parameter('replacements', utils.MappingType) @specs.parameter('count', int) @specs.inject('str_func', yaqltypes.Delegate('str')) @specs.method @specs.name('replace') def replace_with_dict(string, str_func, replacements, count=-1): """:yaql:replace Returns a string with all occurrences of replacements' keys replaced with corresponding replacements' values. If count is specified, only the first count occurrences of every key are replaced. :signature: string.replace(replacements, count => -1) :receiverArg string: input string :argType string: string :arg replacements: dict of replacements in format {old => new ...} :argType replacements: mapping :arg count: how many first occurrences of every key are replaced. -1 by default, which means to do all replacements :argType count: integer :returnType: string .. code:: yaql> "abc ab abc".replace({abc => xx, ab => yy}) "xx yy xx" yaql> "abc ab abc".replace({ab => yy, abc => xx}) "yyc yy yyc" yaql> "abc ab abc".replace({ab => yy, abc => xx}, 1) "yyc ab xx" """ for key, value in six.iteritems(replacements): string = string.replace(str_func(key), str_func(value), count) return string @specs.parameter('__format_string', yaqltypes.String()) @specs.extension_method def format_(__format_string, *args, **kwargs): """:yaql:format Returns a string formatted with positional and keyword arguments. :signature: string.format([args], {kwargs}) :receiverArg string: input string for formatting. Can be passed only as first positional argument if used as a function. Can contain literal text or replacement fields marked by braces {}. Every replacement field should contain either the numeric index of a positional argument or the name of a keyword argument :argType string: string :arg [args]: values for replacements for numeric markers :argType [args]: chain of strings :arg {kwargs}: values for keyword replacements :argType {kwargs}: chain of key-value arguments, where values are strings :returnValue: string .. code:: yaql> "abc{0}ab{1}abc".format(" ", ",") "abc ab,abc" yaql> "abc{foo}ab{bar}abc".format(foo => " ", bar => ",") "abc ab,abc" yaql> format("abc{0}ab{foo}abc", ' ', foo => ",") "abc ab,abc" """ return __format_string.format(*args, **kwargs) @specs.parameter('left', yaqltypes.String()) @specs.parameter('right', int) @specs.name('#operator_*') def string_by_int(left, right, engine): """:yaql:operator * Returns string repeated count times. :signature: left * right :arg left: left operand :argType left: string :arg right: right operator, how many times repeat input string :argType right: integer :returnType: string .. code:: yaql> "ab" * 2 "abab" """ utils.limit_memory_usage(engine, (-right + 1, u''), (right, left)) return left * right @specs.parameter('left', yaqltypes.String()) @specs.parameter('right', yaqltypes.String()) @specs.name('#operator_in') def in_(left, right): """:yaql:operator in Returns true if there is at least one occurrence of left string in right. :signature: left in right :arg left: left operand, which occurrence is checked :argType left: string :arg right: right operand :argType right: string :returnType: boolean .. code:: yaql> "ab" in "abc" true yaql> "ab" in "acb" false """ return left in right @specs.parameter('left', int) @specs.parameter('right', yaqltypes.String()) @specs.name('#operator_*') def int_by_string(left, right, engine): """:yaql:operator * Returns string repeated count times. :signature: left * right :arg left: left operand, how many times repeat input string :argType left: integer :arg right: right operator :argType right: string :returnType: string .. code:: yaql> 2 * "ab" "abab" """ return string_by_int(right, left, engine) @specs.parameter('string', yaqltypes.String()) @specs.parameter('start', int) @specs.parameter('length', int) @specs.method def substring(string, start, length=-1): """:yaql:substring Returns a substring beginning from start index ending with start+end index. :signature: string.substring(start, length => -1) :receiverArg string: input string :argType string: string :arg start: index for substring to start with :argType start: integer :arg length: length of substring. -1 by default, which means end of substring to be equal to the end of input string :argType length: integer :returnType: string .. code:: yaql> "abcd".substring(1) "bcd" yaql> "abcd".substring(1, 2) "bc" """ if length < 0: length = len(string) if start < 0: start += len(string) return string[start:start + length] @specs.parameter('string', yaqltypes.String()) @specs.parameter('sub', yaqltypes.String()) @specs.parameter('start', int) @specs.method def index_of(string, sub, start=0): """:yaql:indexOf Returns an index of first occurrence sub in string beginning from start. -1 is a return value if there is no any occurrence. :signature: string.indexOf(sub, start => 0) :receiverArg string: input string :argType string: string :arg sub: substring to find in string :argType sub: string :arg start: index to start search with, 0 by default :argType start: integer :returnType: integer .. code:: yaql> "cabcdab".indexOf("ab") 1 yaql> "cabcdab".indexOf("ab", 2) 5 yaql> "cabcdab".indexOf("ab", 6) -1 """ return string.find(sub, start) @specs.parameter('string', yaqltypes.String()) @specs.parameter('sub', yaqltypes.String()) @specs.parameter('start', int) @specs.parameter('length', int) @specs.method def index_of_(string, sub, start, length): """:yaql:indexOf Returns an index of first occurrence sub in string beginning from start ending with start+length. -1 is a return value if there is no any occurrence. :signature: string.indexOf(sub, start, length) :receiverArg string: input string :argType string: string :arg sub: substring to find in string :argType sub: string :arg start: index to start search with, 0 by default :argType start: integer :arg length: length of string to find substring in :argType length: integer :returnType: integer .. code:: yaql> "cabcdab".indexOf("bc", 2, 2) 2 """ if start < 0: start += len(string) if length < 0: length = len(string) - start return string.find(sub, start, start + length) @specs.parameter('string', yaqltypes.String()) @specs.parameter('sub', yaqltypes.String()) @specs.parameter('start', int) @specs.method def last_index_of(string, sub, start=0): """:yaql:lastIndexOf Returns an index of last occurrence sub in string beginning from start. -1 is a return value if there is no any occurrence. :signature: string.lastIndexOf(sub, start => 0) :receiverArg string: input string :argType string: string :arg sub: substring to find in string :argType sub: string :arg start: index to start search with, 0 by default :argType start: integer :returnType: integer .. code:: yaql> "cabcdab".lastIndexOf("ab") 5 """ return string.rfind(sub, start) @specs.parameter('string', yaqltypes.String()) @specs.parameter('sub', yaqltypes.String()) @specs.parameter('start', int) @specs.parameter('length', int) @specs.method def last_index_of_(string, sub, start, length): """:yaql:lastIndexOf Returns an index of last occurrence sub in string beginning from start ending with start+length. -1 is a return value if there is no any occurrence. :signature: string.lastIndexOf(sub, start, length) :receiverArg string: input string :argType string: string :arg sub: substring to find in string :argType sub: string :arg start: index to start search with, 0 by default :argType start: integer :arg length: length of string to find substring in :argType length: integer :returnType: integer .. code:: yaql> "cabcdbc".lastIndexOf("bc", 2, 5) 5 """ if start < 0: start += len(string) if length < 0: length = len(string) - start return string.rfind(sub, start, start + length) @specs.parameter('string', yaqltypes.String()) @specs.method def to_char_array(string): """:yaql:toCharArray Converts a string to array of one character strings. :signature: string.toCharArray() :receiverArg string: input string :argType string: string :returnType: list .. code:: yaql> "abc de".toCharArray() ["a", "b", "c", " ", "d", "e"] """ return tuple(string) def characters( digits=False, hexdigits=False, ascii_lowercase=False, ascii_uppercase=False, ascii_letters=False, letters=False, octdigits=False, punctuation=False, printable=False, lowercase=False, uppercase=False, whitespace=False): """:yaql:characters Returns a list of all distinct items of specified types. :signature: characters(digits => false, hexdigits => false, asciiLowercase => false, asciiUppercase => false, asciiLetters => false, letters => false, octdigits => false, punctuation => false, printable => false, lowercase => false, uppercase => false, whitespace => false) :arg digits: include digits in output list if true, false by default :argType digits: boolean :arg hexdigits: include hexademical digits in output list if true, false by default :argType hexdigits: boolean :arg asciiLowercase: include ASCII lowercase letters in output list if true, false by default :argType asciiLowercase: boolean :arg asciiUppercase: include ASCII uppercase letters in output list if true, false by default :argType asciiUppercase: boolean :arg asciiLetters: include both ASCII lowercase and uppercase letters in output list if true, false by default :argType asciiLetters: boolean :arg letters: include both lowercase and uppercase letters in output list if true, false by default :argType letters: boolean :arg octdigits: include digits from 0 to 7 in output list if true, false by default :argType octdigits: boolean :arg punctuation: include ASCII characters, which are considered punctuation, in output list if true, false by default :argType punctuation: boolean :arg printable: include digits, letters, punctuation, and whitespace in output list if true, false by default :argType printable: boolean :arg lowercase: include lowercase letters in output list if true, false by default :argType lowercase: boolean :arg uppercase: include uppercase letters in output list if true, false by default :argType uppercase: boolean :arg whitespace: include all characters that are considered whitespace in output list if true, false by default :argType whitespace: boolean :returnType: list .. code:: yaql> characters(digits => true) ["1", "0", "3", "2", "5", "4", "7", "6", "9", "8"] """ string = '' if digits: string += string_module.digits if hexdigits: string += string_module.hexdigits if ascii_lowercase: string += string_module.ascii_lowercase if ascii_uppercase: string += string_module.ascii_uppercase if ascii_letters: string += string_module.ascii_letters if letters: string += string_module.letters if octdigits: string += string_module.octdigits if punctuation: string += string_module.punctuation if printable: string += string_module.printable if lowercase: string += string_module.lowercase if uppercase: string += string_module.uppercase if whitespace: string += string_module.whitespace return tuple(set(string)) def is_string(arg): """:yaql:isString Returns true if arg is a string. :signature: isString(arg) :arg arg: input value :argType arg: any :returnType: boolean .. code:: yaql> isString("ab") true yaql> isString(1) false """ return isinstance(arg, six.string_types) @specs.parameter('string', yaqltypes.String()) @specs.parameter('prefixes', yaqltypes.String()) @specs.method def starts_with(string, *prefixes): """:yaql:startsWith Returns true if a string starts with any of given args. :signature: string.startsWith([args]) :receiverArg string: input string :argType string: string :arg [args]: chain of strings to check input string with :argType [args]: strings :returnType: boolean .. code:: yaql> "abcd".startsWith("ab", "xx") true yaql> "abcd".startsWith("yy", "xx", "zz") false """ return string.startswith(prefixes) @specs.parameter('string', yaqltypes.String()) @specs.parameter('suffixes', yaqltypes.String()) @specs.method def ends_with(string, *suffixes): """:yaql:endsWith Returns true if a string ends with any of given args. :signature: string.endsWith([args]) :receiverArg string: input string :argType string: string :arg [args]: chain of strings to check input string with :argType [args]: strings :returnType: boolean .. code:: yaql> "abcd".endsWith("cd", "xx") true yaql> "abcd".endsWith("yy", "xx", "zz") false """ return string.endswith(suffixes) @specs.parameter('num', yaqltypes.Number(nullable=True)) def hex_(num): """:yaql:hex Returns a string with hexadecimal representation of num. :signature: hex(num) :arg num: input number to be converted to hexademical :argType num: number :returnType: string .. code:: yaql> hex(256) "0x100" """ return hex(num) def register(context): context.register_function(gt) context.register_function(lt) context.register_function(gte) context.register_function(lte) context.register_function(len_) context.register_function(to_lower) context.register_function(to_upper) context.register_function(split) context.register_function(right_split) context.register_function(join) context.register_function(join_) context.register_function(str_) context.register_function(concat) context.register_function(concat, name='#operator_+') context.register_function(trim) context.register_function(trim_left) context.register_function(trim_right) context.register_function(replace) context.register_function(replace_with_dict) context.register_function(format_) context.register_function(is_empty) context.register_function(string_by_int) context.register_function(int_by_string) context.register_function(substring) context.register_function(index_of) context.register_function(index_of_) context.register_function(last_index_of) context.register_function(last_index_of_) context.register_function(to_char_array) context.register_function(characters) context.register_function(is_string) context.register_function(norm) context.register_function(in_) context.register_function(starts_with) context.register_function(ends_with) context.register_function(hex_) yaql-1.1.3/yaql/standard_library/system.py000066400000000000000000000254521305545650400206710ustar00rootroot00000000000000# 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. """ The module describes main system functions for working with objects. """ import itertools import six from yaql.language import contexts from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes @specs.parameter('name', yaqltypes.StringConstant()) @specs.name('#get_context_data') def get_context_data(name, context): """:yaql:getContextData Returns the context value by its name. This function is system and can be overridden to change the way of getting context data. :signature: getContextData(name) :arg name: value's key name :argType name: string :returnType: any (value type) """ return context[name] @specs.parameter('expr', yaqltypes.Lambda(method=True)) @specs.name('#operator_.') def op_dot(receiver, expr): """:yaql:operator . Returns expr evaluated on receiver. :signature: receiver.expr :arg receiver: object to evaluate expression :argType receiver: any :arg expr: expression :argType expr: expression that can be evaluated as a method :returnType: expression result type .. code:: yaql> [0, 1].select($+1) [1, 2] """ return expr(receiver) @specs.parameter('expr', yaqltypes.YaqlExpression()) @specs.inject('operator', yaqltypes.Delegate('#operator_.')) @specs.name('#operator_?.') def elvis_operator(operator, receiver, expr): """:yaql:operator ?. Evaluates expr on receiver if receiver isn't null and returns the result. If receiver is null returns null. :signature: receiver?.expr :arg receiver: object to evaluate expression :argType receiver: any :arg expr: expression :argType expr: expression that can be evaluated as a method :returnType: expression result or null .. code:: yaql> [0, 1]?.select($+1) [1, 2] yaql> null?.select($+1) null """ if receiver is None: return None return operator(receiver, expr) @specs.parameter('sequence', yaqltypes.Iterable()) @specs.parameter('args', yaqltypes.String()) @specs.method def unpack(sequence, context, *args): """:yaql:unpack Returns context object with sequence values unpacked to args. If args size is equal to sequence size then args get appropriate sequence values. If args size is 0 then args are 1-based indexes. Otherwise ValueError is raised. :signature: sequence.unpack([args]) :receiverArg sequence: iterable of items to be passed as context values :argType sequence: iterable :arg [args]: keys to be associated with sequence items. If args size is equal to sequence size then args get appropriate sequence items. If args size is 0 then args are indexed start from 1. Otherwise exception will be thrown :argType [args]: chain of strings :returnType: context object .. code:: yaql> [1, 2].unpack(a, b) -> $a + $b 3 yaql> [2, 3].unpack() -> $1 + $2 5 """ lst = tuple(itertools.islice(sequence, len(args) + 1)) if 0 < len(args) != len(lst): raise ValueError('Cannot unpack {0} elements into {1}'.format( len(lst), len(args))) if len(args) > 0: for i in range(len(lst)): context[args[i]] = lst[i] else: for i, t in enumerate(sequence, 1): context[str(i)] = t return context def with_(context, *args): """:yaql:with Returns new context object where args are stored with 1-based indexes. :signature: with([args]) :arg [args]: values to be stored under appropriate numbers $1, $2, ... :argType [args]: chain of any values :returnType: context object .. code:: yaql> with("ab", "cd") -> $1 + $2 "abcd" """ for i, t in enumerate(args, 1): context[str(i)] = t return context @specs.inject('__context__', yaqltypes.Context()) def let(__context__, *args, **kwargs): """:yaql:let Returns context object where args are stored with 1-based indexes and kwargs values are stored with appropriate keys. :signature: let([args], {kwargs}) :arg [args]: values to be stored under appropriate numbers $1, $2, ... :argType [args]: chain of any values :arg {kwargs}: values to be stored under appropriate keys :argType {kwargs}: chain of mappings :returnType: context object .. code:: yaql> let(1, 2, a => 3, b => 4) -> $1 + $a + $2 + $b 10 """ for i, value in enumerate(args, 1): __context__[str(i)] = value for key, value in six.iteritems(kwargs): __context__[key] = value return __context__ @specs.parameter('name', yaqltypes.String()) @specs.parameter('func', yaqltypes.Lambda()) def def_(name, func, context): """:yaql:def Returns new context object with function name defined. :signature: def(name, func) :arg name: name of function :argType name: string :arg func: function to be stored under provided name :argType func: lambda :returnType: context object .. code:: yaql> def(sq, $*$) -> [1, 2, 3].select(sq($)) [1, 4, 9] """ @specs.name(name) def wrapper(*args, **kwargs): return func(*args, **kwargs) context.register_function(wrapper) return context @specs.parameter('left', contexts.ContextBase) @specs.parameter('right', yaqltypes.Lambda(with_context=True)) @specs.name('#operator_->') def send_context(left, right): """:yaql:operator -> Evaluates lambda on provided context and returns the result. :signature: left -> right :arg left: context to be used for function :argType left: context object :arg right: function :argType right: lambda :returnType: any (function return value type) .. code:: yaql> let(a => 1) -> $a 1 """ return right(left) @specs.method @specs.parameter('condition', yaqltypes.Lambda()) @specs.parameter('message', yaqltypes.String()) def assert__(engine, obj, condition, message=u'Assertion failed'): """:yaql:assert Evaluates condition against object. If it evaluates to true returns the object, otherwise throws an exception with provided message. :signature: obj.assert(condition, message => "Assertion failed") :arg obj: object to evaluate condition on :argType obj: any :arg condition: lambda function to be evaluated on obj. If result of function evaluates to false then trows exception message :argType condition: lambda :arg message: message to trow if condition returns false :argType message: string :returnType: obj type or message .. code:: yaql> 12.assert($ < 2) Execution exception: Assertion failed yaql> 12.assert($ < 20) 12 yaql> [].assert($, "Failed assertion") Execution exception: Failed assertion """ if utils.is_iterator(obj): obj = utils.memorize(obj, engine) if not condition(obj): raise AssertionError(message) return obj @specs.name('#call') @specs.parameter('callable_', yaqltypes.PythonType( object, False, validators=(six.callable,))) def call(callable_, *args, **kwargs): """:yaql:call Evaluates function with specified args and kwargs and returns the result. This function is used to transform expressions like '$foo(args, kwargs)' to '#call($foo, args, kwargs)'. Note that to use this functionality 'delegate' mode has to be enabled. :signature: call(callable, args, kwargs) :arg callable: callable function :argType callable: python type :arg args: sequence of items to be used for calling :argType args: sequence :arg kwargs: dictionary with kwargs to be used for calling :argType kwargs: mapping :returnType: any (callable return type) """ return callable_(*args, **kwargs) @specs.parameter('func', yaqltypes.Lambda()) def lambda_(func): """:yaql:lambda Constructs a new anonymous function Note that to use this function 'delegate' mode has to be enabled. :signature: lambda(func) :arg func: function to be returned :argType func: lambda :returnType: obj type or message .. code:: yaql> let(func => lambda(2 * $)) -> [1, 2, 3].select($func($)) [2, 4, 6] yaql> [1, 2, 3, 4].where(lambda($ > 3)($ + 1)) [3, 4] """ return func @specs.name('#operator_.') @specs.parameter('name', yaqltypes.Keyword()) @specs.inject('func', yaqltypes.Delegate(use_convention=False)) def get_property(func, obj, name): """:yaql:operator . Returns value of 'name' property. :signature: left.right :arg left: object :argType left: any :arg right: object property name :argType right: keyword :returnType: any .. code:: yaql> now().year 2016 """ func_name = '#property#{0}'.format(name) return func(func_name, obj) @specs.name('call') @specs.parameter('name', yaqltypes.String()) @specs.parameter('args', yaqltypes.Sequence()) @specs.parameter('kwargs', utils.MappingType) def call_func(context, engine, name, args, kwargs, receiver=utils.NO_VALUE): """:yaql:call Evaluates function name with specified args and kwargs and returns the result. :signature: call(name, args, kwargs) :arg name: name of callable :argType name: string :arg args: sequence of items to be used for calling :argType args: sequence :arg kwargs: dictionary with kwargs to be used for calling :argType kwargs: mapping :returnType: any (callable return type) .. code:: yaql> call(let, [1, 2], {a => 3, b => 4}) -> $1 + $a + $2 + $b 10 """ return context(name, engine, receiver)( *args, **utils.filter_parameters_dict(kwargs)) def register(context, delegates=False): context.register_function(get_context_data) context.register_function(op_dot) context.register_function(unpack) context.register_function(with_) context.register_function(send_context) context.register_function(let) context.register_function(def_) context.register_function(elvis_operator) context.register_function(assert__) context.register_function(call_func) if delegates: context.register_function(call) context.register_function(lambda_) def register_fallbacks(context): context.register_function(get_property) yaql-1.1.3/yaql/standard_library/yaqlized.py000066400000000000000000000146721305545650400211710ustar00rootroot00000000000000# Copyright (c) 2016 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. """ Any Python class or object can be yaqlized. It is possible to call methods, access attributes/properties and index of yaqlized objects. The first way to yaqlize object is using function call: .. code-block:: python class A(object): foo = 256 def bar(self): print('yaqlization works with methods too') sample_object = A() yaqlization.yaqlize(sample_object) The second way is using decorator: .. code-block:: python @yaqlization.yaqlize class A(object): foo = 256 def bar(self): print('yaqlization works with methods too') Any mentioned operation on yaqlized objects can be disabled with additional parameters for yaqlization. Also it is possible to specify whitelist/blacklist of methods/attributes/keys that are exposed to the yaql. This module provides implemented operators on Yaqlized objects. """ import re import six from yaql.language import expressions from yaql.language import runner from yaql.language import specs from yaql.language import utils from yaql.language import yaqltypes from yaql import yaqlization REGEX_TYPE = type(re.compile('.')) class Yaqlized(yaqltypes.GenericType): def __init__(self, can_access_attributes=False, can_call_methods=False, can_index=False): def check_value(value, context, *args, **kwargs): settings = yaqlization.get_yaqlization_settings(value) if settings is None: return False if can_access_attributes and not settings['yaqlizeAttributes']: return False if can_call_methods and not settings['yaqlizeMethods']: return False if can_index and not settings['yaqlizeIndexer']: return False return True super(Yaqlized, self).__init__(checker=check_value, nullable=False) def _match_name_to_entry(name, entry): if name == entry: return True elif isinstance(entry, REGEX_TYPE): return entry.search(name) is not None elif six.callable(entry): return entry(name) return False def _validate_name(name, settings, exception_cls=AttributeError): if name.startswith('_'): raise exception_cls('Cannot access ' + name) whitelist = settings['whitelist'] if whitelist: for entry in whitelist: if _match_name_to_entry(name, entry): return raise exception_cls('Cannot access ' + name) blacklist = settings['blacklist'] if blacklist: for entry in blacklist: if _match_name_to_entry(name, entry): raise exception_cls('Cannot access ' + name) def _remap_name(name, settings): return settings['attributeRemapping'].get(name, name) def _auto_yaqlize(value, settings): if not settings['autoYaqlizeResult']: return if isinstance(value, type): cls = value else: cls = type(value) if cls.__module__ == int.__module__: return try: yaqlization.yaqlize(value, auto_yaqlize_result=True) except Exception: pass @specs.parameter('receiver', Yaqlized(can_call_methods=True)) @specs.parameter('expr', yaqltypes.YaqlExpression(expressions.Function)) @specs.name('#operator_.') def op_dot(receiver, expr, context, engine): """:yaql:operator . Evaluates expression on receiver and returns its result. :signature: receiver.expr :arg receiver: yaqlized receiver :argType receiver: yaqlized object, initialized with yaqlize_methods equal to True :arg expr: expression to be evaluated :argType expr: expression :returnType: any (expression return type) """ settings = yaqlization.get_yaqlization_settings(receiver) mappings = _remap_name(expr.name, settings) _validate_name(expr.name, settings) if not isinstance(mappings, six.string_types): name = mappings[0] if len(mappings) > 0: arg_mappings = mappings[1] else: arg_mappings = {} else: name = mappings arg_mappings = {} func = getattr(receiver, name) args, kwargs = runner.translate_args(False, expr.args, {}) args = tuple(arg(utils.NO_VALUE, context, engine) for arg in args) for key, value in six.iteritems(kwargs): kwargs[arg_mappings.get(key, key)] = value( utils.NO_VALUE, context, engine) res = func(*args, **kwargs) _auto_yaqlize(res, settings) return res @specs.parameter('obj', Yaqlized(can_access_attributes=True)) @specs.parameter('attr', yaqltypes.Keyword()) @specs.name('#operator_.') def attribution(obj, attr): """:yaql:operator . Returns attribute of the object. :signature: obj.attr :arg obj: yaqlized object :argType obj: yaqlized object, initialized with yaqlize_attributes equal to True :arg attr: attribute name :argType attr: keyword :returnType: any """ settings = yaqlization.get_yaqlization_settings(obj) _validate_name(attr, settings) attr = _remap_name(attr, settings) res = getattr(obj, attr) _auto_yaqlize(res, settings) return res @specs.parameter('obj', Yaqlized(can_index=True)) @specs.name('#indexer') def indexation(obj, key): """:yaql:operator indexer Returns value of attribute/property key of the object. :signature: obj[key] :arg obj: yaqlized object :argType obj: yaqlized object, initialized with yaqlize_indexer equal to True :arg key: index name :argType key: keyword :returnType: any """ settings = yaqlization.get_yaqlization_settings(obj) _validate_name(key, settings, KeyError) res = obj[key] _auto_yaqlize(res, settings) return res def register(context): context = context.create_child_context() context.register_function(op_dot) context.register_function(attribution) context.register_function(indexation) return context yaql-1.1.3/yaql/tests/000077500000000000000000000000001305545650400146015ustar00rootroot00000000000000yaql-1.1.3/yaql/tests/__init__.py000066400000000000000000000062671305545650400167250ustar00rootroot00000000000000# 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 testtools import yaql from yaql.language import factory from yaql import legacy class TestCase(testtools.TestCase): _default_engine = None _default_legacy_engine = None engine_options = { 'yaql.limitIterators': 100, 'yaql.memoryQuota': 20000, 'yaql.convertTuplesToLists': True, 'yaql.convertSetsToLists': True } legacy_engine_options = { 'yaql.limitIterators': 100, 'yaql.memoryQuota': 20000, } def create_engine(self): func = TestCase._default_engine if func is None: engine_factory = factory.YaqlFactory(allow_delegates=True) TestCase._default_engine = func = engine_factory.create( options=self.engine_options) return func def create_legacy_engine(self): func = TestCase._default_legacy_engine if func is None: engine_factory = legacy.YaqlFactory() TestCase._default_legacy_engine = func = engine_factory.create( options=self.legacy_engine_options) return func @property def context(self): if self._context is None: self._context = yaql.create_context(delegates=True) return self._context @property def legacy_context(self): if self._legacy_context is None: self._legacy_context = legacy.create_context() return self._legacy_context @context.setter def context(self, value): self._context = value @property def engine(self): if self._engine is None: self._engine = self.create_engine() return self._engine @property def legacy_engine(self): if self._legacy_engine is None: self._legacy_engine = self.create_legacy_engine() return self._legacy_engine def setUp(self): self._context = None self._engine = None self._legacy_context = None self._legacy_engine = None super(TestCase, self).setUp() def eval(self, expression, data=None, context=None): expr = self.engine(expression) context = context or self.context context['data'] = data return expr.evaluate(data=data, context=context) def legacy_eval(self, expression, data=None, context=None): expr = self.legacy_engine(expression) return expr.evaluate(data=data, context=context or self.legacy_context) def legacy_eval_new_engine(self, expression, data=None, context=None): expr = self.engine(expression) return expr.evaluate(data=data, context=context or self.legacy_context) yaql-1.1.3/yaql/tests/test_boolean.py000066400000000000000000000050061305545650400176320ustar00rootroot00000000000000# 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 yaql.tests class TestBoolean(yaql.tests.TestCase): def test_and(self): self.assertTrue(self.eval('true and true')) self.assertFalse(self.eval('true and false')) self.assertFalse(self.eval('false and false')) self.assertFalse(self.eval('false and true')) self.assertEqual(12, self.eval('true and 12')) self.assertFalse(self.eval('null and null')) def test_or(self): self.assertTrue(self.eval('true or true')) self.assertTrue(self.eval('true or false')) self.assertFalse(self.eval('false or false')) self.assertTrue(self.eval('false or true')) self.assertEqual(12, self.eval('12 or true')) self.assertFalse(self.eval('null or null')) def test_not(self): self.assertFalse(self.eval('not true')) self.assertTrue(self.eval('not false')) self.assertTrue(self.eval('not 0')) self.assertFalse(self.eval('not 123')) self.assertTrue(self.eval("not ''")) self.assertFalse(self.eval("not True")) self.assertTrue(self.eval('not null')) def test_lazy(self): self.assertEqual(1, self.eval('$ or 10/($-1)', data=1)) self.assertEqual(0, self.eval('$ and 10/$', data=0)) def test_boolean_equality(self): self.assertTrue(self.eval('false = false')) self.assertTrue(self.eval('false != true')) self.assertTrue(self.eval('true != false')) self.assertTrue(self.eval('true = true')) self.assertFalse(self.eval('true = false')) self.assertFalse(self.eval('false = true')) self.assertFalse(self.eval('false != false')) self.assertFalse(self.eval('true != true')) def test_is_boolean(self): self.assertTrue(self.eval('isBoolean(true)')) self.assertTrue(self.eval('isBoolean(false)')) self.assertFalse(self.eval('isBoolean(123)')) self.assertFalse(self.eval('isBoolean(abc)')) yaql-1.1.3/yaql/tests/test_branching.py000066400000000000000000000045041305545650400201500ustar00rootroot00000000000000# 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 yaql.tests class TestBranching(yaql.tests.TestCase): def test_switch(self): expr = 'switch($ < 10 => 1, $ >= 10 and $ < 100 => 2, $ >= 100 => 3)' self.assertEqual(3, self.eval(expr, data=123)) self.assertEqual(2, self.eval(expr, data=50)) self.assertEqual(1, self.eval(expr, data=-123)) def test_select_case(self): expr = 'selectCase($ < 10, $ >= 10 and $ < 100)' self.assertEqual(2, self.eval(expr, data=123)) self.assertEqual(1, self.eval(expr, data=50)) self.assertEqual(0, self.eval(expr, data=-123)) def test_select_all_cases(self): expr = 'selectAllCases($ < 10, $ > 5)' self.assertEqual([0], self.eval(expr, data=1)) self.assertEqual([0, 1], self.eval(expr, data=7)) self.assertEqual([1], self.eval(expr, data=12)) def test_examine(self): expr = 'examine($ < 10, $ > 5)' self.assertEqual([True, False], self.eval(expr, data=1)) self.assertEqual([True, True], self.eval(expr, data=7)) self.assertEqual([False, True], self.eval(expr, data=12)) def test_switch_case(self): expr = "$.switchCase('a', 'b', 'c')" self.assertEqual('a', self.eval(expr, data=0)) self.assertEqual('b', self.eval(expr, data=1)) self.assertEqual('c', self.eval(expr, data=3)) self.assertEqual('c', self.eval(expr, data=30)) self.assertEqual('c', self.eval(expr, data=-30)) def test_coalesce(self): self.assertEqual(2, self.eval('coalesce($, 2)', data=None)) self.assertEqual(1, self.eval('coalesce($, 2)', data=1)) self.assertEqual(2, self.eval('coalesce($, $, 2)', data=None)) self.assertIsNone(self.eval('coalesce($)', data=None)) yaql-1.1.3/yaql/tests/test_collections.py000066400000000000000000000433011305545650400205310ustar00rootroot00000000000000# 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. from yaql.language import exceptions import yaql.tests class TestCollections(yaql.tests.TestCase): def test_list(self): self.assertEqual([], self.eval('list()')) self.assertEqual([1, 2, 3], self.eval('list(1, 2, 3)')) self.assertEqual([1, 2, [3, 4]], self.eval('list(1, 2, list(3, 4))')) def test_list_expr(self): self.assertEqual([1, 2, 3], self.eval('[1,2,3]')) self.assertEqual([2, 4], self.eval('[1,[2]][1] + [3, [4]][1]')) self.assertEqual([1, 2, 3, 4], self.eval('[1,2] + [3, 4]')) self.assertEqual(2, self.eval('([1,2] + [3, 4])[1]')) self.assertEqual([], self.eval('[]')) def test_list_from_iterator(self): iterator = (i for i in range(3)) self.assertEqual([0, 1, 2], self.eval('list($)', data=iterator)) def test_to_list(self): data = (i for i in range(3)) self.assertEqual([0, 1, 2], self.eval('$.toList()', data=data)) data = [0, 1, 2] self.assertEqual([0, 1, 2], self.eval('$.toList()', data=data)) data = (0, 1, 2) self.assertEqual([0, 1, 2], self.eval('$.toList()', data=data)) data = {'a': 1, 'b': 2} self.assertTrue(self.eval('isList($.keys().toList())', data=data)) def test_list_concatenates_and_flatten_generators(self): generator_func = lambda: (i for _ in range(2) for i in range(3)) self.context['$seq1'] = generator_func() self.context['$seq2'] = generator_func() self.assertEqual([0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2], self.eval('list($seq1, $seq2)')) def test_indexer_list_access(self): data = [1, 2, 3] self.assertEqual(1, self.eval('$[0]', data=data)) self.assertEqual(3, self.eval('$[-1]', data=data)) self.assertEqual(2, self.eval('$[-1-1]', data=data)) self.assertRaises(IndexError, self.eval, "$[3]", data=data) self.assertRaises(IndexError, self.eval, "$[-4]", data=data) self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, "$[a]", data=data) def test_dict(self): self.assertEqual( {'b c': 13, 'a': 2, 4: 5, None: None, True: False, 8: 8}, self.eval("dict(a => 2, 'b c' => 13, 4 => 5, " "null => null, true => false, 2+6=>8)")) def test_dict_expr(self): self.assertEqual( {'b c': 13, 'a': 2, 4: 5, None: None, True: False, 8: 8}, self.eval("{a => 2, 'b c' => 13, 4 => 5, " "null => null, true => false, 2+6=>8}")) self.assertEqual({'b': 2, 'a': 1}, self.eval('{a => 1} + {b=>2}')) self.assertEqual({}, self.eval('{}')) def test_dict_from_sequence(self): self.assertEqual({'a': 1, 'b': 2}, self.eval("dict(list(list(a, 1), list('b', 2)))")) generator = (i for i in (('a', 1), ['b', 2])) self.assertEqual({'a': 1, 'b': 2}, self.eval('dict($)', data=generator)) def test_to_dict(self): self.assertEqual({1: 1, 2: 4, 3: 9}, self.eval('$.toDict($, $*$)', data=[1, 2, 3])) def test_keyword_dict_access(self): data = {'A': 12, 'b c': 44, '__d': 99, '_e': 999} self.assertEqual(12, self.eval('$.A', data=data)) self.assertEqual(999, self.eval('$._e', data=data)) self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, "$.'b c'", data=data) self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, '$.123', data=data) self.assertRaises(KeyError, self.eval, '$.b', data=data) self.assertRaises( exceptions.YaqlLexicalException, self.eval, '$.__d', data=data) def test_indexer_dict_access(self): data = {'a': 12, 'b c': 44} self.assertEqual(12, self.eval('$[a]', data=data)) self.assertEqual(44, self.eval("$['b c']", data=data)) self.assertRaises(KeyError, self.eval, "$[b]", data=data) def test_indexer_dict_access_with(self): data = {'a': 12, 'b c': 44} self.assertEqual(55, self.eval('$[c, 55]', data=data)) self.assertEqual(66, self.eval('$[c, default => 66]', data=data)) def test_list_eq(self): self.assertTrue(self.eval('[c, 55]=[c, 55]')) self.assertFalse(self.eval('[c, 55]=[55, c]')) self.assertFalse(self.eval('[c, 55]=null')) self.assertFalse(self.eval('null = [c, 55]')) def test_list_neq(self): self.assertFalse(self.eval('[c, 55] != [c, 55]')) self.assertTrue(self.eval('[c, 55] != [55, c]')) self.assertTrue(self.eval('[c, 55] != null')) self.assertTrue(self.eval('null != [c, 55]')) def test_dict_eq(self): self.assertTrue(self.eval('{a => [c, 55]} = {a => [c, 55]}')) self.assertTrue(self.eval('{[c, 55] => a} = {[c, 55] => a}')) self.assertFalse(self.eval('{[c, 55] => a} = {[c, 56] => a}')) self.assertFalse(self.eval('{[c, 55] => a, b => 1} = {[c, 55] => a}')) self.assertFalse(self.eval('{[c, 55] => a} = null')) def test_dict_neq(self): self.assertFalse(self.eval('{a => [c, 55]} != {a => [c, 55]}')) self.assertFalse(self.eval('{[c, 55] => a} != {[c, 55] => a}')) self.assertTrue(self.eval('{[c, 55] => a} != {[c, 56] => a}')) self.assertTrue(self.eval('{[c, 55] => a, b => 1} != {[c, 55] => a}')) self.assertTrue(self.eval('{[c, 55] => a} != null')) def test_dict_get(self): data = {'a': 12, 'b c': 44} self.assertEqual(12, self.eval('$.get(a)', data=data)) self.assertIsNone(self.eval('$.get(b)', data=data)) self.assertEqual(50, self.eval('$.get(c, 50)', data=data)) def test_dict_set(self): data = {'a': 12, 'b c': 44} self.assertEqual( {'a': 99, 'b c': 44, 'x': None}, self.eval('$.set(a, 99).set(x, null)', data=data)) self.assertEqual(data, {'a': 12, 'b c': 44}) def test_dict_set_many(self): data = {'a': 12, 'b c': 44} self.assertEqual( {'a': 55, 'b c': 44, 'd x': 99, None: None}, self.eval('$.set(dict(a => 55, "d x" => 99, null => null))', data=data)) self.assertEqual(data, {'a': 12, 'b c': 44}) def test_dict_set_many_inline(self): data = {'a': 12, 'b c': 44} self.assertEqual( {'a': 55, 'b c': 44, 'd x': 99}, self.eval('$.set(a => 55, "d x" => 99)', data=data)) self.assertEqual(data, {'a': 12, 'b c': 44}) def test_dict_keys(self): data = {'a': 12, 'b': 44} self.assertItemsEqual(['a', 'b'], self.eval('$.keys()', data=data)) def test_dict_values(self): data = {'a': 12, 'b': 44} self.assertItemsEqual([12, 44], self.eval('$.values()', data=data)) def test_dict_items(self): data = {'a': 12, 'b': 44} self.assertItemsEqual([['a', 12], ['b', 44]], self.eval('$.items()', data=data)) self.assertEqual(data, self.eval('dict($.items())', data=data)) def test_in(self): data = {'a': 12, 'b': 44} self.assertTrue(self.eval('44 in $.values()', data=data)) self.assertTrue(self.eval('24 in $.values().select(2*$)', data=data)) self.assertTrue(self.eval('{a => 2} in {{a => 2} => {b => 3}}.keys()')) self.assertTrue(self.eval('5 in set(1, 2, 5)')) self.assertTrue(self.eval('[1, 2] in set([1, 2], 5)')) self.assertTrue(self.eval('5 in [1, 2, 5]')) self.assertTrue(self.eval('[1, 2] in {[1, 2] => [3, 4]}.keys()')) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'a in $', data=data) def test_contains(self): data = {'a': 12, 'b': 44} self.assertTrue(self.eval('$.containsKey(a)', data=data)) self.assertTrue(self.eval('$.values().contains(44)', data=data)) self.assertFalse(self.eval('$.containsKey(null)', data=data)) self.assertTrue(self.eval( '$.values().select(2*$).contains(24)', data=data)) self.assertTrue(self.eval('set(1, 2, 5).contains(5)')) self.assertTrue(self.eval('[1, 2, 5].contains(5)')) self.assertTrue( self.eval('{{a => 2} => {b => 3}}.containsKey({a => 2})')) self.assertTrue(self.eval('{[1, 2] => [3, 4]}.containsKey([1, 2])')) self.assertTrue(self.eval('{[1, 2] => [3, 4]}.containsValue([3, 4])')) self.assertTrue(self.eval('set([1, 2], 5).contains([1, 2])')) def test_list_addition(self): self.assertEqual( [1, 2, 3, 4], self.eval('list(1, 2) + list(3, 4)')) self.assertEqual( [1, 2, 6, 8], self.eval('list(1, 2) + list(3, 4).select($ * 2)')) def test_dict_addition(self): self.assertEqual( {'a': 1, 'b': 2}, self.eval('dict(a => 1) + dict(b => 2)')) def test_list_multiplication(self): self.assertItemsEqual( [1, 2, 1, 2, 1, 2], self.eval('3 * [1, 2]')) self.assertItemsEqual( [1, 2, 1, 2, 1, 2], self.eval('[1, 2] * 3')) def test_dict_list_key(self): self.assertEqual( 3, self.eval('dict($ => 3).get($)', data=[1, 2])) self.assertEqual( 3, self.eval('dict($ => 3).get($)', data=[1, [2]])) def test_dict_dict_key(self): self.assertEqual( 3, self.eval('dict($ => 3).get($)', data={'a': 1})) def test_delete(self): self.assertEqual( [2, 3, 4], self.eval('[1, 2, 3, 4].delete(0)')) self.assertEqual( [3, 4], self.eval('[1, 2, 3, 4].delete(0, 2)')) self.assertEqual( [4], self.eval('[1, 2, 3, 4].delete(0, 2).delete(0)')) self.assertEqual( [1], self.eval('[1, 2, 3, 4].delete(1, -1)')) self.assertEqual( [1, 2, 3, 4], self.eval('[1, 2, 3, 4].delete(0, 0)')) self.assertEqual( [], self.eval('[1, 2, 3, 4].delete(0, -1)')) def test_insert(self): self.assertEqual( [1, 'a', 2], self.eval('[1, 2].insert(1, a)')) self.assertEqual( [1, ['a', 'b'], 2], self.eval('[1, 2].insert(1, [a, b])')) self.assertEqual( [1, 'a', 2], self.eval('[1, 2].insert(-1, a)')) self.assertEqual( [1, 2, 'a'], self.eval('[1, 2].insert(100, a)')) self.assertEqual( ['b', 'a'], self.eval('[].insert(0, a).insert(0, b)')) self.assertRaises( exceptions.NoMatchingMethodException, self.eval, 'set(a, b).insert(1, a)') def test_insert_iter(self): self.assertEqual( [1, 'a', 2], self.eval('[1, 2].select($).insert(1, a)')) self.assertEqual( [1, ['a', 'b'], 2], self.eval('[1, 2].select($).insert(1, [a, b])')) self.assertEqual( [1, 2], self.eval('[1, 2].select($).insert(-1, a)')) self.assertEqual( [1, 2, 'a'], self.eval('[1, 2].select($).insert(100, a)')) self.assertEqual( ['b', 'a'], self.eval('[].select($).insert(0, a).insert(0, b)')) self.assertEqual( ['a', 'a', 'b'], self.eval('set(a, b).orderBy($).insert(1, a)')) def test_insert_many(self): self.assertEqual( [1, 'a', 'b', 2], self.eval('[1, 2].insertMany(1, [a, b])')) self.assertEqual( ['a', 'b', 1, 2], self.eval('[1, 2].insertMany(-1, [a, b])')) self.assertEqual( [1, 2, 'a', 'b'], self.eval('[1, 2].insertMany(100, [a, b])')) self.assertEqual( ['a', 'a', 'b', 'b'], self.eval('[].insertMany(0, [a, b]).insertMany(1, [a, b])')) def test_replace(self): self.assertEqual( [None, 2, 3, 4], self.eval('[1, 2, 3, 4].replace(0, null)')) self.assertEqual( [None, 3, 4], self.eval('[1, 2, 3, 4].replace(0, null, 2)')) self.assertEqual( [1, 7], self.eval('[1, 2, 3, 4].replace(1, 7, -1)')) self.assertEqual( 7, self.eval('set(1, 2, 3, 4).replace(1, 7)')[1]) self.assertEqual( [1, 7, 3, 4], self.eval('set(4, 2, 3, 1).orderBy($).replace(1, 7)')) def test_replace_many(self): self.assertEqual( [7, 8, 2, 3, 4], self.eval('[1, 2, 3, 4].replaceMany(0, [7, 8])')) self.assertEqual( [7, 8, 3, 4], self.eval('[1, 2, 3, 4].replaceMany(0, [7, 8], 2)')) self.assertEqual( [1, 7, 8], self.eval('[1, 2, 3, 4].replaceMany(1, [7, 8], -1)')) def test_delete_dict(self): data = {'a': 1, 'b': 2, 'c': 3, 'd': 4} self.assertEqual( {'a': 1, 'd': 4}, self.eval('$.delete(b, c)', data=data)) def test_delete_all(self): data = {'a': 1, 'b': 2, 'c': 3, 'd': 4} self.assertEqual( {'a': 1, 'd': 4}, self.eval('$.deleteAll([b, c])', data=data)) def test_set(self): self.assertItemsEqual([2, 1, 3], self.eval('set(1, 2, 3, 2, 1)')) self.assertEqual([[1, 2, 3, 2, 1]], self.eval('set([1, 2, 3, 2, 1])')) self.assertEqual([], self.eval('set()')) self.assertEqual( [{'a': {'b': 'c'}}], self.eval('set({a => {b => c}})')) def test_set_from_iterator(self): self.assertItemsEqual([2, 1, 3], self.eval('set([1, 2, 3].select($))')) def test_to_set(self): self.assertItemsEqual( [2, 1, 3], self.eval('[1, 2, 3].select($).toSet()')) self.assertItemsEqual( [2, 1, 3], self.eval('[1, 2, 3].toSet()')) def test_set_len(self): self.assertEqual(3, self.eval('set(1, 2, 3).len()')) self.assertEqual(3, self.eval('len(set(1, 2, 3))')) def test_set_addition(self): self.assertItemsEqual( [4, 3, 2, 1], self.eval('set(1, 2, 3) + set(4, 2, 3)')) self.assertTrue( self.eval('isSet(set(1, 2, 3) + set(4, 2, 3))')) def test_set_union(self): self.assertItemsEqual( [4, 3, 2, 1], self.eval('set(1, 2, 3).union(set(4, 2, 3))')) def test_set_eq(self): self.assertTrue(self.eval('set(1, 2, 3) = set(3, 2, 1)')) self.assertFalse(self.eval('set(1, 2, 3) = set(1, 2, 3, 4)')) def test_set_neq(self): self.assertFalse(self.eval('set(1, 2, 3) != set(3, 2, 1)')) self.assertTrue(self.eval('set(1, 2, 3) != set(1, 2, 3, 4)')) def test_set_lt(self): self.assertTrue(self.eval('set(1, 2, 3) < set(1, 2, 3, 4)')) self.assertFalse(self.eval('set(1, 2, 3) < set(1, 2, 5)')) def test_set_gt(self): self.assertTrue(self.eval('set(1, 2, 3, 4) > set(1, 2, 3)')) self.assertFalse(self.eval('set(1, 2, 3) > set(1, 2, 3)')) def test_set_gte(self): self.assertFalse(self.eval('set(1, 2, 4) >= set(1, 2, 3)')) self.assertTrue(self.eval('set(1, 2, 3) >= set(1, 2, 3)')) def test_set_lte(self): self.assertFalse(self.eval('set(1, 2, 3) <= set(1, 2, 4)')) self.assertTrue(self.eval('set(1, 2, 3) <= set(1, 2, 3)')) def test_set_difference(self): self.assertItemsEqual( [4, 1], self.eval('set(1, 2, 3, 4).difference(set(2, 3))')) def test_set_subtraction(self): self.assertItemsEqual( [4, 1], self.eval('set(1, 2, 3, 4) - set(2, 3)')) self.assertTrue( self.eval('isSet(set(1, 2, 3, 4) - set(2, 3))')) def test_set_symmetric_difference(self): self.assertItemsEqual( [4, 1, 5], self.eval('set(1, 2, 3, 4).symmetricDifference(set(2, 3, 5))')) def test_set_add(self): self.assertItemsEqual( [4, 1, 2, 3], self.eval('set(1, 2, 3).add(4)')) self.assertItemsEqual( [4, 1, 2, 3, 5], self.eval('set(1, 2, 3).add(4, 5)')) self.assertItemsEqual( [1, 3, 2, [1, 2]], self.eval('set(1, 2, 3).add([1, 2])')) self.assertItemsEqual( [4, 1, None, 2, 3, 5], self.eval('set(1, 2, 3).add(4, 5, null)')) self.assertTrue( self.eval('isSet(set(1, 2, 3).add(4, 5, null))')) def test_set_remove(self): self.assertItemsEqual( [1, 3], self.eval('set(1, 2, 3).remove(2)')) self.assertItemsEqual( [3, None], self.eval('set(1, 2, null, 3).remove(1, 2, 5)')) self.assertItemsEqual( [3], self.eval('set(1, 2, null, 3).remove(1, 2, 5, null)')) self.assertItemsEqual( [1, 3, 2], self.eval('set(1, 2, 3, [1, 2]).remove([1, 2])')) self.assertTrue( self.eval('isSet(set(1, 2, 3, [1, 2]).remove([1, 2]))')) yaql-1.1.3/yaql/tests/test_common.py000066400000000000000000000057741305545650400175170ustar00rootroot00000000000000# 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 yaql.tests class TestCommon(yaql.tests.TestCase): def test_null(self): self.assertIsNone(self.eval('null')) def test_true(self): res = self.eval('true') self.assertTrue(res) self.assertIsInstance(res, bool) def test_false(self): res = self.eval('false') self.assertFalse(res) self.assertIsInstance(res, bool) def test_string(self): self.assertEqual('True', self.eval('True')) self.assertEqual('some string', self.eval("'some string'")) def test_null_to_null(self): self.assertTrue(self.eval('null = null')) self.assertFalse(self.eval('null != null')) self.assertTrue(self.eval('null <= null')) self.assertTrue(self.eval('null >= null')) self.assertFalse(self.eval('null < null')) self.assertFalse(self.eval('null > null')) def test_ordering(self): self.assertTrue(self.eval('null < 0')) self.assertTrue(self.eval('null < true')) self.assertTrue(self.eval('null < false')) self.assertTrue(self.eval('null < a')) self.assertTrue(self.eval('null <= 0')) self.assertFalse(self.eval('null > 0')) self.assertFalse(self.eval('null >= 0')) self.assertTrue(self.eval('null != 0')) self.assertTrue(self.eval('null != false')) self.assertFalse(self.eval('null = false')) self.assertFalse(self.eval('null = 0')) self.assertFalse(self.eval('0 < null')) self.assertFalse(self.eval('0 <= null')) self.assertTrue(self.eval('0 >= null')) self.assertTrue(self.eval('0 > null')) def test_max(self): self.assertEqual(5, self.eval('max(1, 5)')) self.assertEqual(-1, self.eval('max(null, -1)')) self.assertIsNone(self.eval('max(null, null)')) def test_min(self): self.assertEqual(1, self.eval('min(1, 5)')) self.assertIsNone(self.eval('min(null, -1)')) self.assertIsNone(self.eval('min(null, null)')) def test_comparision_of_incomparable(self): self.assertFalse(self.eval('a = 1')) self.assertFalse(self.eval('a = false')) self.assertFalse(self.eval('a = null')) self.assertFalse(self.eval('[a] = [false]')) self.assertTrue(self.eval('a != 1')) self.assertTrue(self.eval('a != false')) self.assertTrue(self.eval('[a] != [false]')) self.assertTrue(self.eval('a != null')) yaql-1.1.3/yaql/tests/test_contexts.py000066400000000000000000000255131305545650400200670ustar00rootroot00000000000000# 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 testtools from testtools import matchers from yaql.language import contexts from yaql.language import specs class TestContexts(testtools.TestCase): def test_store_data(self): context = contexts.Context() context['key'] = 123 self.assertEqual(123, context['key']) def test_name_normalization(self): context = contexts.Context() context['key'] = 123 self.assertEqual(123, context['$key']) def test_empty_name(self): context = contexts.Context() context[''] = 123 self.assertEqual(123, context['$']) self.assertEqual(123, context['']) self.assertEqual(123, context['$1']) def test_missing_key_access(self): context = contexts.Context() self.assertIsNone(context['key']) def test_key_deletion(self): context = contexts.Context() context['key'] = 123 del context['key'] self.assertIsNone(context['key']) def test_child_contexts(self): context = contexts.Context() context2 = context.create_child_context() context['key'] = 123 self.assertEqual(123, context2['key']) context2['key'] = 345 self.assertEqual(345, context2['key']) del context2['key'] self.assertEqual(123, context2['key']) def test_get_functions(self): def f(): pass def f_(): pass def f__(): pass def g(): pass context = contexts.Context() context2 = context.create_child_context() context.register_function(f) context.register_function(f_) context.register_function(g, exclusive=True) context2.register_function(f__) functions, is_exclusive = context.get_functions('f') self.assertFalse(is_exclusive) self.assertIsInstance(functions, set) self.assertThat(functions, testtools.matchers.HasLength(2)) self.assertThat( functions, matchers.AllMatch(matchers.IsInstance( specs.FunctionDefinition))) functions, is_exclusive = context2.get_functions('g') self.assertFalse(is_exclusive) functions, is_exclusive = context2.get_functions('f') self.assertFalse(is_exclusive) self.assertThat(functions, testtools.matchers.HasLength(1)) def test_collect_functions(self): def f(): pass def f_(): pass def f__(): pass context = contexts.Context() context2 = context.create_child_context() context3 = context2.create_child_context() context.register_function(f) context.register_function(f_) context3.register_function(f__) functions = context3.collect_functions('f') self.assertThat(functions, testtools.matchers.HasLength(2)) self.assertThat(functions[0], testtools.matchers.HasLength(1)) self.assertThat(functions[1], testtools.matchers.HasLength(2)) def test_function_in(self): def f(): pass def f_(): pass def f__(): pass context = contexts.Context() context2 = context.create_child_context() context3 = context2.create_child_context() context.register_function(f) context.register_function(f_) context3.register_function(f__) functions = context3.collect_functions('f') self.assertNotIn(specs.get_function_definition(f__), context3) self.assertIn(functions[0].pop(), context3) self.assertNotIn(functions[1].pop(), context3) def test_data(self): context = contexts.Context() context2 = context.create_child_context() context['key'] = 123 context2['key2'] = 321 self.assertIn('key', context) self.assertIn('key2', context2) self.assertIn('$key', context) self.assertNotIn('key2', context) self.assertNotIn('key', context2) def test_keys(self): context = contexts.Context() context2 = context.create_child_context() context['key'] = 123 context2['key2'] = 321 keys = list(context2.keys()) self.assertThat(keys, testtools.matchers.HasLength(1)) self.assertEqual(keys[0], '$key2') def test_delete_function(self): def f(): pass def f_(): pass context = contexts.Context() context.register_function(f) context2 = context.create_child_context() context2.register_function(f_) functions, is_exclusive = context2.get_functions('f') spec = functions.pop() self.assertIn(spec, context2) context2.delete_function(spec) self.assertNotIn(spec, context2) functions, is_exclusive = context.get_functions('f') self.assertThat(functions, matchers.HasLength(1)) @staticmethod def create_multi_context(): def f(): pass def f_(): pass def f__(): pass def f___(): pass context = contexts.Context() context2 = context.create_child_context() context3 = context2.create_child_context() context4 = contexts.Context() context5 = context4.create_child_context() mc = contexts.MultiContext([context3, context5]) context.register_function(f) context2.register_function(f___) context4.register_function(f_) context5.register_function(f__) context3['key'] = 'context3' context5['key'] = 'context5' context4['key2'] = 'context4' context['key3'] = 'context1' mc['key4'] = 'mc' return mc def test_multi_context_data(self): mc = self.create_multi_context() self.assertEqual(mc['key'], 'context3') self.assertEqual(mc['key2'], 'context4') self.assertEqual(mc['key3'], 'context1') def test_multi_context_data_in(self): mc = self.create_multi_context() self.assertIn('key', mc) self.assertIn('key4', mc) self.assertNotIn('key2', mc) self.assertIn('key2', mc.parent) self.assertNotIn('key3', mc.parent) self.assertNotIn('key4', mc.parent) self.assertIn('key3', mc.parent.parent) self.assertIsNone(mc.parent.parent.parent) def test_multi_context_keys(self): mc = self.create_multi_context() self.assertItemsEqual(['$key4', '$key'], mc.keys()) self.assertItemsEqual(['$key2'], mc.parent.keys()) self.assertItemsEqual(['$key3'], mc.parent.parent.keys()) def test_multi_context_get_functions(self): def f(): pass mc = self.create_multi_context() mc.register_function(f) functions, is_exclusive = mc.get_functions('f') self.assertFalse(is_exclusive) self.assertThat(functions, matchers.HasLength(2)) functions, is_exclusive = mc.parent.get_functions('f') self.assertFalse(is_exclusive) self.assertThat(functions, matchers.HasLength(2)) functions, is_exclusive = mc.parent.parent.get_functions('f') self.assertFalse(is_exclusive) self.assertThat(functions, matchers.HasLength(1)) def test_multi_context_collect_functions(self): def f(): pass mc = self.create_multi_context() mc.register_function(f) levels = mc.collect_functions('f') self.assertThat(levels, matchers.HasLength(3)) self.assertThat(levels[0], matchers.HasLength(2)) self.assertThat(levels[1], matchers.HasLength(2)) self.assertThat(levels[2], matchers.HasLength(1)) def test_multi_context_function_in(self): mc = self.create_multi_context() functions, is_exclusive = mc.get_functions('f') for fd in functions: self.assertIn(fd, mc) def test_multi_context_delete_data(self): mc = self.create_multi_context() del mc['key'] self.assertNotIn('key', mc) def test_multi_context_function_delete(self): mc = self.create_multi_context() functions, is_exclusive = mc.get_functions('f') for fd in functions: mc.delete_function(fd) functions, is_exclusive = mc.get_functions('f') self.assertThat(functions, matchers.HasLength(0)) @staticmethod def create_linked_context(): def f(): pass def g(): pass def g_(): pass def g__(): pass context1 = contexts.Context() context2 = contexts.Context() context1.register_function(f) context1.register_function(g) context2.register_function(g_) context1['key'] = 'context1' context1['key1'] = 'context1' context2['key'] = 'context2' context2['key2'] = 'context2' context3 = context2.create_child_context() context3.register_function(g__) context3['key'] = 'context3' context2['key3'] = 'context3' return contexts.LinkedContext( parent_context=context1, linked_context=context3) def test_linked_context_data(self): mc = self.create_linked_context() self.assertEqual(mc['key'], 'context3') self.assertEqual(mc['key2'], 'context2') self.assertEqual(mc['key3'], 'context3') self.assertEqual(mc['key3'], 'context3') self.assertEqual(mc.parent['key'], 'context2') self.assertEqual(mc.parent.parent['key'], 'context1') def test_linked_context_collect_functions(self): mc = self.create_linked_context() self.assertThat(mc.collect_functions('f'), matchers.HasLength(1)) levels = mc.collect_functions('g') self.assertThat(levels, matchers.HasLength(3)) self.assertThat(levels[0], matchers.HasLength(1)) self.assertThat(levels[1], matchers.HasLength(1)) self.assertThat(levels[2], matchers.HasLength(1)) def test_linked_context_delete_data(self): mc = self.create_linked_context() self.assertIn('key', mc) del mc['key'] self.assertNotIn('key', mc) def test_linked_context_function_in(self): mc = self.create_linked_context() functions, is_exclusive = mc.get_functions('f') for fd in functions: self.assertIn(fd, mc) yaql-1.1.3/yaql/tests/test_datetime.py000066400000000000000000000164551305545650400200210ustar00rootroot00000000000000# 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 datetime import time from dateutil import tz from testtools import matchers import yaql.tests DT = datetime.datetime TS = datetime.timedelta class TestDatetime(yaql.tests.TestCase): def test_build_datetime_components(self): dt = DT(2015, 8, 29, tzinfo=tz.tzutc()) self.assertEqual( dt, self.eval('datetime(2015, 8, 29)')) self.assertEqual( dt, self.eval('datetime(year => 2015, month => 8, day => 29,' 'hour => 0, minute => 0, second => 0, ' 'microsecond => 0)')) def test_build_datetime_iso(self): self.assertEqual( DT(2015, 8, 29, tzinfo=tz.tzutc()), self.eval('datetime("2015-8-29")') ) self.assertEqual( DT(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tz.tzutc()), self.eval('datetime("2008-09-03T20:56:35.450686")') ) self.assertEqual( DT(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tz.tzutc()), self.eval('datetime("2008-09-03T20:56:35.450686Z")') ) self.assertEqual( DT(2008, 9, 3, 0, 0, tzinfo=tz.tzutc()), self.eval('datetime("20080903")') ) dt = self.eval('datetime("2008-09-03T20:56:35.450686+03:00")') self.assertEqual( DT(2008, 9, 3, 20, 56, 35, 450686), dt.replace(tzinfo=None) ) self.assertEqual(TS(hours=3), dt.utcoffset()) def test_build_datetime_string(self): self.assertEqual( DT(2006, 11, 21, 16, 30, tzinfo=tz.tzutc()), self.eval('datetime("Tuesday, 21. November 2006 04:30PM", ' '"%A, %d. %B %Y %I:%M%p")') ) def test_datetime_fields(self): dt = DT(2006, 11, 21, 16, 30, tzinfo=tz.tzutc()) self.assertEqual(2006, self.eval('$.year', dt)) self.assertEqual(11, self.eval('$.month', dt)) self.assertEqual(21, self.eval('$.day', dt)) self.assertEqual(16, self.eval('$.hour', dt)) self.assertEqual(30, self.eval('$.minute', dt)) self.assertEqual(0, self.eval('$.second', dt)) self.assertEqual(0, self.eval('$.microsecond', dt)) self.assertEqual(1164126600, self.eval('$.timestamp', dt)) self.assertEqual(1, self.eval('$.weekday', dt)) self.assertEqual(TS(), self.eval('$.offset', dt)) self.assertEqual(TS(hours=16, minutes=30), self.eval('$.time', dt)) self.assertEqual(dt.replace(hour=0, minute=0), self.eval('$.date', dt)) self.assertEqual(dt, self.eval('$.utc', dt)) def test_build_timespan(self): self.assertEqual(TS(0), self.eval('timespan()')) self.assertEqual( TS(1, 7384, 5006), self.eval('timespan(days => 1, hours => 2, minutes => 3, ' 'seconds => 4, milliseconds => 5, microseconds => 6)')) self.assertEqual( TS(1, 7384, 4994), self.eval('timespan(days => 1, hours => 2, minutes => 3, ' 'seconds =>4, milliseconds => 5, microseconds => -6)')) self.assertEqual( TS(microseconds=-1000), self.eval('timespan(milliseconds => -1)')) def test_datetime_from_timestamp(self): dt = DT(2006, 11, 21, 16, 30, tzinfo=tz.tzutc()) self.assertEqual(dt, self.eval('datetime(1164126600)')) def test_replace(self): dt = DT(2006, 11, 21, 16, 30, tzinfo=tz.tzutc()) self.assertEqual( DT(2009, 11, 21, 16, 40, tzinfo=tz.tzutc()), self.eval('$.replace(year => 2009, minute => 40)', dt)) def test_timespan_fields(self): ts = TS(1, 51945, 5000) self.assertAlmostEqual(1.6, self.eval('$.days', ts), places=2) self.assertAlmostEqual(38.43, self.eval('$.hours', ts), places=2) self.assertAlmostEqual(2305.75, self.eval('$.minutes', ts), places=2) self.assertAlmostEqual(138345, self.eval('$.seconds', ts), places=1) self.assertEqual(138345005, self.eval('$.milliseconds', ts)) self.assertEqual(138345005000, self.eval('$.microseconds', ts)) def test_now(self): self.assertIsInstance(self.eval('now()'), DT) self.assertIsInstance(self.eval('now(utctz())'), DT) self.assertIsInstance(self.eval('now(localtz())'), DT) self.assertThat( self.eval('now(utctz()) - now()'), matchers.LessThan(TS(seconds=1)) ) self.assertTrue(self.eval('now(localtz()).offset = localtz()')) def test_datetime_math(self): self.context['dt1'] = self.eval('now()') time.sleep(0.1) self.context['dt2'] = self.eval('now()') delta = TS(milliseconds=120) self.assertIsInstance(self.eval('$dt2 - $dt1'), TS) self.assertThat(self.eval('$dt2 - $dt1'), matchers.LessThan(delta)) self.assertTrue(self.eval('($dt2 - $dt1) + $dt1 = $dt2')) self.assertTrue(self.eval('$dt1 + ($dt2 - $dt1) = $dt2')) self.assertThat( self.eval('($dt2 - $dt1) * 2'), matchers.LessThan(2 * delta)) self.assertThat( self.eval('2.1 * ($dt2 - $dt1)'), matchers.LessThan(2 * delta)) self.assertTrue(self.eval('-($dt1 - $dt2) = +($dt2 - $dt1)')) self.assertTrue(self.eval('$dt2 > $dt1')) self.assertTrue(self.eval('$dt2 >= $dt1')) self.assertTrue(self.eval('$dt2 != $dt1')) self.assertTrue(self.eval('$dt1 = $dt1')) self.assertTrue(self.eval('$dt1 < $dt2')) self.assertTrue(self.eval('$dt1 <= $dt2')) self.assertEqual(-1, self.eval('($dt2 - $dt1) / ($dt1 - $dt2)')) self.assertTrue(self.eval('$dt2 - ($dt2 - $dt1) = $dt1')) self.assertEqual( 0, self.eval('($dt2 - $dt1) - ($dt2 - $dt1)').total_seconds()) delta2 = self.eval('($dt2 - $dt1) / 2.1') self.assertThat(delta2, matchers.LessThan(delta / 2)) self.assertTrue(self.eval('$dt1 + $ < $dt2', delta2)) self.assertTrue(self.eval('$ + $dt1 < $dt2', delta2)) self.assertTrue(self.eval('$dt2 - $dt1 > $', delta2)) self.assertTrue(self.eval('$dt2 - $dt1 >= $', delta2)) self.assertTrue(self.eval('$dt2 - $dt1 != $', delta2)) self.assertFalse(self.eval('$dt2 - $dt1 < $', delta2)) self.assertFalse(self.eval('$dt2 - $dt1 <= $', delta2)) self.assertTrue(self.eval('($dt2 - $dt1) + $ > $', delta2)) def test_is_datetime(self): self.assertTrue(self.eval('isDatetime(datetime("2015-8-29"))')) self.assertFalse(self.eval('isDatetime(123)')) self.assertFalse(self.eval('isDatetime(abc)')) def test_is_timespan(self): self.assertTrue(self.eval('isTimespan(timespan(milliseconds => -1))')) self.assertFalse(self.eval('isTimespan(123)')) self.assertFalse(self.eval('isTimespan(abc)')) yaql-1.1.3/yaql/tests/test_engine.py000066400000000000000000000252741305545650400174710ustar00rootroot00000000000000# 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 sys import six import yaql from yaql.language import exceptions from yaql.language import factory from yaql.language import specs from yaql.language import yaqltypes from yaql import tests class TestEngine(tests.TestCase): def test_parser_grammar(self): # replace stderr with cString to write to copy = sys.stderr sys.stderr = six.StringIO() try: debug_opts = dict(self.engine_options) debug_opts['yaql.debug'] = True yaql.factory.YaqlFactory().create(options=debug_opts) sys.stderr.seek(0) err_out = sys.stderr.read() self.assertEqual('Generating LALR tables\n', err_out) finally: # put stderr back sys.stderr = copy def test_no_function_registered(self): self.assertRaises( exceptions.NoFunctionRegisteredException, self.eval, 'kjhfksjdhfk()') def test_no_method_registered(self): self.assertRaises( exceptions.NoMethodRegisteredException, self.eval, '[1,2].kjhfksjdhfk($)') def test_no_matching_function(self): self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'len(1, 2, 3)') def test_mapping_translation_exception(self): self.context.register_function( lambda *args, **kwargs: 1, name='f') self.assertRaises( exceptions.MappingTranslationException, self.eval, 'f(2+2 => 4)') def test_no_matching_method(self): self.assertRaises( exceptions.NoMatchingMethodException, self.eval, '[1, 2].select(1, 2, 3)') def test_duplicate_parameters(self): def raises(): @specs.parameter('p') @specs.parameter('p') def f(p): return p self.assertRaises( exceptions.DuplicateParameterDecoratorException, raises) def test_invalid_parameter(self): def raises(): @specs.parameter('x') def f(p): return p self.assertRaises( exceptions.NoParameterFoundException, raises) def test_lexical_error(self): self.assertRaises( exceptions.YaqlLexicalException, self.eval, '1 ? 2') def test_grammar_error(self): self.assertRaises( exceptions.YaqlGrammarException, self.eval, '1 2') self.assertRaises( exceptions.YaqlGrammarException, self.eval, '(2') def test_invalid_method(self): self.assertRaises( exceptions.InvalidMethodException, self.context.register_function, lambda: 1, name='f', method=True) @specs.parameter('x', yaqltypes.Lambda()) def func(x): return x self.assertRaises( exceptions.InvalidMethodException, self.context.register_function, func, name='f2', method=True) def test_function_definition(self): def func(a, b, *args, **kwargs): return a, b, args, kwargs fd = specs.get_function_definition(func) self.assertEqual( (1, 2, (5, 7), {'kw1': 'x', 'kw2': None}), fd(self.engine, self.context)( 1, 2, 5, 7, kw1='x', kw2=None)) self.assertEqual( (1, 5, (), {}), fd(self.engine, self.context)(1, b=5)) def test_eval(self): self.assertEqual( 120, yaql.eval( 'let(inp => $) -> [1, 2, 3].select($ + $inp).reduce($1 * $2)', data=3) ) def test_skip_args(self): def func(a=11, b=22, c=33): return a, b, c self.context.register_function(func) self.assertEqual([11, 22, 1], self.eval('func(,,1)')) self.assertEqual([0, 22, 1], self.eval('func(0,,1)')) self.assertEqual([11, 22, 33], self.eval('func()')) self.assertEqual([11, 1, 4], self.eval('func(,1, c=>4)')) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'func(,1, b=>4)') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'func(,1,, c=>4)') def test_no_trailing_commas(self): self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(1,,)') self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(,1,)') self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(,,)') self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(,)') def test_no_varargs_after_kwargs(self): self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(x=>y, t)') self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(x=>y, ,t)') self.assertRaises(exceptions.YaqlGrammarException, self.eval, 'func(a, x=>y, ,t)') def test_super(self): @specs.parameter('string', yaqltypes.String()) @specs.inject('base', yaqltypes.Super()) def len2(string, base): return 2 * base(string) context = self.context.create_child_context() context.register_function(len2, name='len') self.assertEqual(6, self.eval('len(abc)', context=context)) def test_delegate_factory(self): @specs.parameter('name', yaqltypes.String()) @specs.inject('__df__', yaqltypes.Delegate()) def call_func(__df__, name, *args, **kwargs): return __df__(name, *args, **kwargs) context = self.context.create_child_context() context.register_function(call_func) self.assertEqual( [1, 2], self.eval('callFunc(list, 1, 2)', context=context)) self.assertEqual( 6, self.eval("callFunc('#operator_*', 2, 3)", context=context)) def test_keyword_constant_function(self): @specs.parameter('arg', yaqltypes.Keyword()) def foo(arg): return arg context = self.context.create_child_context() context.register_function(foo) self.assertEqual('qw', self.eval('foo(qw)', context=context)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', context=context, data='asd') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo("q w")', context=context) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo(null)', context=context) def test_constant_function(self): @specs.parameter('arg', yaqltypes.Constant(True)) def foo(arg): return arg context = self.context.create_child_context() context.register_function(foo) self.assertEqual('qw', self.eval('foo(qw)', context=context)) self.assertEqual(123, self.eval('foo(123)', context=context)) self.assertFalse(self.eval('foo(false)', context=context)) self.assertIsNone(self.eval('foo(null)', context=context)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', context=context, data='asd') def test_string_constant_function(self): @specs.parameter('arg', yaqltypes.StringConstant()) def foo(arg): return arg context = self.context.create_child_context() context.register_function(foo) self.assertEqual('qw', self.eval('foo(qw)', context=context)) self.assertEqual('q w', self.eval('foo("q w")', context=context)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', context=context, data='asd') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo(123)', context=context) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo(null)', context=context) def test_numeric_constant_function(self): @specs.parameter('arg', yaqltypes.NumericConstant()) def foo(arg): return arg context = self.context.create_child_context() context.register_function(foo) self.assertEqual(123, self.eval('foo(123)', context=context)) self.assertEqual(12.4, self.eval('foo(12.4)', context=context)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', context=context, data=5) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo("123")', context=context) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo(null)', context=context) def test_boolean_constant_function(self): @specs.parameter('arg', yaqltypes.BooleanConstant()) def foo(arg): return arg context = self.context.create_child_context() context.register_function(foo) self.assertTrue(self.eval('foo(true)', context=context)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', context=context, data=True) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo("true")', context=context) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo(null)', context=context) def test_high_precedence_operator_insertion(self): # Test that insertion of high precedence operator doesn't # make turn ($.a)[0] into $.(a[0]) engine_factory = factory.YaqlFactory(allow_delegates=True) engine_factory.insert_operator( None, True, ':', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True) engine = engine_factory.create() data = {'a': [1]} expr = engine('$.a[0]') self.assertEqual(1, expr.evaluate(context=self.context, data=data)) yaql-1.1.3/yaql/tests/test_legacy.py000066400000000000000000000134131305545650400174600ustar00rootroot00000000000000# 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. from yaql.language import exceptions import yaql.tests class TestLegacyNewEngine(yaql.tests.TestCase): def __init__(self, *args, **kwargs): super(TestLegacyNewEngine, self).__init__(*args, **kwargs) self.eval = self.legacy_eval_new_engine def test_dict(self): self.assertEqual( {'a': 'b', 1: 2, None: None}, self.eval('dict(1 => 2, a => b, null => null)')) self.assertEqual({}, self.eval('dict()')) def test_list(self): self.assertEqual([1, 2, 'a', None], self.eval('list(1, 2, a, null)')) self.assertEqual([], self.eval('list()')) self.assertEqual([1, 2], self.eval('[1, 2].select($).list()')) def test_dict_get(self): self.assertEqual(5, self.eval("get($, 'a b')", data={'a b': 5})) self.assertEqual(5, self.eval("$.get('a b')", data={'a b': 5})) def test_int(self): self.assertEqual(5, self.eval("'5'.int()")) self.assertEqual(5, self.eval('5.2.int()')) self.assertEqual(0, self.eval('null.int()')) def test_float(self): self.assertAlmostEqual(5.1, self.eval("'5.1'.float()")) self.assertAlmostEqual(5.0, self.eval('5.float()')) self.assertAlmostEqual(5.1, self.eval("float('5.1')")) self.assertAlmostEqual(5.0, self.eval("float(5)")) self.assertEqual(0, self.eval('null.float()')) def test_bool(self): self.assertFalse(self.eval('null.bool()')) self.assertFalse(self.eval("''.bool()")) self.assertFalse(self.eval('0.bool()')) self.assertFalse(self.eval('false.bool()')) self.assertFalse(self.eval('[].bool()')) self.assertFalse(self.eval('{}.bool()')) self.assertTrue(self.eval("' '.bool()")) self.assertTrue(self.eval('x.bool()')) self.assertTrue(self.eval('1.bool()')) self.assertTrue(self.eval('true.bool()')) self.assertTrue(self.eval('[1].bool()')) self.assertTrue(self.eval('{a=>b}.bool()')) def test_filter(self): self.assertEqual(2, self.eval("list(1,2,3)[1]")) self.assertEqual(3, self.eval("list(1,2,3)[$]", data=2)) self.assertEqual([1, 3], self.eval("list(1,2,3)[$ != 2]")) self.assertEqual([], self.eval("list()[$ > 0]")) def test_sum(self): self.assertEqual(6, self.eval('list(1,2,3).sum()')) self.assertEqual(6, self.eval('sum(list(1,2,3))')) def test_range(self): self.assertEqual([2, 3, 4, 5], self.eval('range(2).take(4)')) self.assertEqual([1, 2, 3], self.eval('range(1, 4)')) self.assertEqual([2, 3, 4, 5], self.eval('2.range().take(4)')) self.assertEqual([1, 2, 3], self.eval('1.range(4)')) def test_take_while(self): self.assertEqual([1, 2], self.eval('[1, 2, 3, 4].takeWhile($ < 3)')) self.assertEqual([1, 2], self.eval('takeWhile([1, 2, 3, 4], $ < 3)')) def test_switch(self): expr = 'switch($, $ > 10 => 1, $ <= 10 => -1)' self.assertEqual(1, self.eval(expr, data=15)) self.assertEqual(-1, self.eval(expr, data=5)) def test_as(self): self.assertEqual( [3, 6], self.eval('[1, 2].as(sum($) => a).select($ * $a)')) def test_distinct(self): data = [1, 2, 3, 2, 4, 8] self.assertEqual([1, 2, 3, 4, 8], self.eval('$.distinct()', data=data)) self.assertEqual([1, 2, 3, 4, 8], self.eval('distinct($)', data=data)) data = [{'a': 1}, {'b': 2}, {'a': 1}] self.assertEqual( [{'a': 1}, {'b': 2}], self.eval('$.distinct()', data=data)) def test_keyword_dict_access(self): data = {'A': 12, 'b c': 44, '__d': 99, '_e': 999} self.assertEqual(12, self.eval('$.A', data=data)) self.assertEqual(999, self.eval('$._e', data=data)) self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, "$.'b c'", data=data) self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, '$.123', data=data) self.assertIsNone(self.eval('$.b', data=data)) self.assertRaises(exceptions.YaqlLexicalException, self.eval, '$.__d', data=data) def test_compare_not_comparable(self): self.assertTrue(self.eval('asd != true')) self.assertFalse(self.eval('asd = 0')) class TestLegacy(TestLegacyNewEngine): def __init__(self, *args, **kwargs): super(TestLegacy, self).__init__(*args, **kwargs) self.eval = self.legacy_eval def test_tuples_func(self): self.assertEqual((1, 2), self.eval('tuple(1, 2)')) self.assertEqual((None,), self.eval('tuple(null)')) self.assertEqual((), self.eval('tuple()')) def test_tuples(self): self.assertEqual((1, 2), self.eval('1 => 2')) self.assertEqual((None, 'a b'), self.eval('null => "a b"')) self.assertEqual((1, 2, 3), self.eval('1 => 2 => 3')) self.assertEqual(((1, 2), 3), self.eval('(1 => 2) => 3')) self.assertEqual((1, (2, 3)), self.eval('1 => (2 => 3)')) def test_dicts_are_iterable(self): data = {'a': 1, 'b': 2} self.assertTrue(self.eval('a in $', data)) self.assertItemsEqual('ab', self.eval('$.sum()', data)) yaql-1.1.3/yaql/tests/test_math.py000066400000000000000000000170261305545650400171510ustar00rootroot00000000000000# 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 yaql.tests class TestMath(yaql.tests.TestCase): def test_binary_plus_int(self): res = self.eval('2 + 3') self.assertEqual(5, res) self.assertIsInstance(res, int) def test_binary_plus_float(self): res = self.eval('2 + 3.0') self.assertEqual(5, res) self.assertIsInstance(res, float) res = self.eval('2.3+3.5') self.assertEqual(5.8, res) self.assertIsInstance(res, float) def test_binary_minus_int(self): res = self.eval('12 -3') self.assertEqual(9, res) self.assertIsInstance(res, int) def test_binary_minus_float(self): res = self.eval('1 - 2.1') self.assertEqual(-1.1, res) self.assertIsInstance(res, float) res = self.eval('123.321- 0.321') self.assertEqual(123.0, res) self.assertIsInstance(res, float) def test_multiplication_int(self): res = self.eval('3 * 2') self.assertEqual(6, res) self.assertIsInstance(res, int) self.assertEqual(-6, self.eval('3 * -2')) self.assertEqual(6, self.eval('-3 * -2')) def test_multiplication_float(self): res = self.eval('3.0 * 2.0') self.assertEqual(6.0, res) self.assertIsInstance(res, float) self.assertAlmostEqual(-6.51, self.eval('3.1 * -2.1')) self.assertAlmostEqual(6.51, self.eval('-3.1 * -2.1')) def test_division(self): self.assertEqual(3, self.eval('7 / 2')) self.assertEqual(-4, self.eval('7 / -2')) self.assertAlmostEqual(2.5, self.eval('5 / 2.0')) self.assertAlmostEqual(2.5, self.eval('5.0 / 2')) self.assertAlmostEqual(-2.5, self.eval('-5.0 / 2.0')) self.assertRaises(ZeroDivisionError, self.eval, '7 / 0') self.assertRaises(ZeroDivisionError, self.eval, '7 / -0.0') def test_brackets(self): self.assertEqual(-4, self.eval('1 - (2) - 3')) self.assertEqual(2, self.eval('1 - (2 - 3)')) def test_unary_minus(self): self.assertEqual(-4, self.eval('-4')) self.assertEqual(-12.0, self.eval('-12.0')) self.assertEqual(4, self.eval('3--1')) self.assertEqual(2, self.eval('3+-1')) self.assertAlmostEqual(4.3, self.eval('3.2 - -1.1')) self.assertEqual(2, self.eval('-(1-3)')) def test_unary_plus(self): self.assertEqual(4, self.eval('+4')) self.assertEqual(12.0, self.eval('+12.0')) self.assertEqual(2, self.eval('3-+1')) self.assertEqual(4, self.eval('3++1')) self.assertAlmostEqual(2.1, self.eval('3.2 - +1.1')) def test_modulo_int(self): res = self.eval('9 mod 5') self.assertEqual(4, res) self.assertIsInstance(res, int) self.assertEqual(-1, self.eval('9 mod -5')) def test_modulo_float(self): res = self.eval('9.0 mod 5') self.assertEqual(4.0, res) self.assertIsInstance(res, float) res = self.eval('9 mod 5.0') self.assertEqual(4.0, res) self.assertIsInstance(res, float) res = self.eval('9.0 mod 5.0') self.assertEqual(4.0, res) self.assertIsInstance(res, float) self.assertAlmostEqual(-1.1, self.eval('9.1 mod -5.1')) def test_abs(self): self.assertEqual(4, self.eval('abs(-4)')) self.assertEqual(4, self.eval('abs(4)')) self.assertEqual(4.4, self.eval('abs(-4.4)')) def test_gt(self): res = self.eval('5 > 3') self.assertIsInstance(res, bool) self.assertTrue(res) self.assertFalse(self.eval('3 > 3')) def test_lt(self): res = self.eval('3 < 5') self.assertIsInstance(res, bool) self.assertTrue(res) self.assertFalse(self.eval('3 < 3')) self.assertTrue(self.eval('2.5 < 3')) def test_gte(self): res = self.eval('5 >= 3') self.assertIsInstance(res, bool) self.assertTrue(res) self.assertTrue(self.eval('3 >= 3')) self.assertTrue(self.eval('3.5 > 3')) self.assertFalse(self.eval('2 >= 3')) def test_lte(self): res = self.eval('3 <= 5') self.assertIsInstance(res, bool) self.assertTrue(res) self.assertTrue(self.eval('3 <= 3')) self.assertFalse(self.eval('3 <= 2')) def test_eq(self): self.assertTrue(self.eval('5 = 5')) self.assertTrue(self.eval('1.0 = 1')) self.assertFalse(self.eval('5 = 6')) def test_neq(self): self.assertFalse(self.eval('5 != 5')) self.assertFalse(self.eval('0 != 0.0')) self.assertTrue(self.eval('5 != 6')) def test_zero_division(self): self.assertRaises(ZeroDivisionError, self.eval, '0/0') def test_random(self): self.assertTrue(self.eval('with(random()) -> $ >= 0 and $ < 1')) self.assertTrue(self.eval('with(random(2, 5)) -> $ >= 2 and $ <= 5')) def test_int(self): self.assertEqual(5, self.eval("int('5')")) self.assertEqual(5, self.eval('int(5.2)')) self.assertEqual(0, self.eval('int(null)')) def test_float(self): self.assertAlmostEqual(-1.23, self.eval("float('-1.23')")) self.assertEqual(0.0, self.eval('float(null)')) def test_bitwise_or(self): self.assertEqual(3, self.eval('bitwiseOr(1, 3)')) self.assertEqual(3, self.eval('bitwiseOr(1, 2)')) def test_bitwise_and(self): self.assertEqual(1, self.eval('bitwiseAnd(1, 3)')) self.assertEqual(0, self.eval('bitwiseAnd(1, 2)')) def test_bitwise_xor(self): self.assertEqual(2, self.eval('bitwiseXor(1, 3)')) self.assertEqual(3, self.eval('bitwiseXor(1, 2)')) def test_bitwise_not(self): self.assertEqual(-2, self.eval('bitwiseNot(1)')) def test_shift_bits_left(self): self.assertEqual(32, self.eval('shiftBitsLeft(1, 5)')) def test_shift_bits_right(self): self.assertEqual(2, self.eval('shiftBitsRight(32, 4)')) self.assertEqual(0, self.eval('shiftBitsRight(32, 6)')) def test_pow(self): self.assertEqual(32, self.eval('pow(2, 5)')) self.assertEqual(4, self.eval('pow(2, 5, 7)')) def test_sign(self): self.assertEqual(1, self.eval('sign(123)')) self.assertEqual(-1, self.eval('sign(-123)')) self.assertEqual(0, self.eval('sign(0)')) def test_round(self): self.assertAlmostEqual(2.0, self.eval('round(2.3)')) self.assertAlmostEqual(2.3, self.eval('round(2.345, 1)')) def test_is_integer(self): self.assertTrue(self.eval('isInteger(-2)')) self.assertTrue(self.eval('isInteger(2)')) self.assertFalse(self.eval('isInteger(2.3)')) self.assertFalse(self.eval('isInteger(abc)')) self.assertFalse(self.eval('isInteger(true)')) def test_is_number(self): self.assertTrue(self.eval('isNumber(-2)')) self.assertTrue(self.eval('isNumber(2)')) self.assertTrue(self.eval('isNumber(2.3)')) self.assertFalse(self.eval('isNumber(abc)')) self.assertFalse(self.eval('isNumber(true)')) yaql-1.1.3/yaql/tests/test_miscellaneous.py000066400000000000000000000061001305545650400210520ustar00rootroot00000000000000# 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. from yaql.language import exceptions from yaql.language import specs from yaql.language import yaqltypes import yaql.tests class TestMiscellaneous(yaql.tests.TestCase): def test_pass_lambda_from_code(self): self.assertEqual( [], list(self.context('where', self.engine, [1, 2, 3])(False)) ) self.assertEqual( [2, 3], list(self.context('where', self.engine, [1, 2, 3])( lambda t: t > 1)) ) def test_bool_is_not_an_integer(self): @specs.parameter('arg', yaqltypes.Integer()) def foo(arg): return arg self.context.register_function(foo) self.assertEqual(2, self.eval('foo(2)')) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo(true)') def test_nullable_collections(self): @specs.parameter('arg', yaqltypes.Sequence()) def foo1(arg): return arg is None @specs.parameter('arg', yaqltypes.Sequence(nullable=True)) def foo2(arg): return arg is None @specs.parameter('arg', yaqltypes.Iterable()) def bar1(arg): return arg is None @specs.parameter('arg', yaqltypes.Iterable(nullable=True)) def bar2(arg): return arg is None @specs.parameter('arg', yaqltypes.Iterator()) def baz1(arg): return arg is None @specs.parameter('arg', yaqltypes.Iterator(nullable=True)) def baz2(arg): return arg is None for func in (foo1, foo2, bar1, bar2, baz1, baz2): self.context.register_function(func) self.assertFalse(self.eval('foo1([1, 2])')) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo1(null)') self.assertFalse(self.eval('foo2([1, 2])')) self.assertTrue(self.eval('foo2(null)')) self.assertFalse(self.eval('bar1([1, 2])')) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'bar1(null)') self.assertFalse(self.eval('bar2([1, 2])')) self.assertTrue(self.eval('bar2(null)')) self.assertFalse(self.eval('baz1($)', data=iter([1, 2]))) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'baz1(null)') self.assertFalse(self.eval('baz2($)', data=iter([1, 2]))) self.assertTrue(self.eval('baz2(null)')) yaql-1.1.3/yaql/tests/test_queries.py000066400000000000000000000422261305545650400176750ustar00rootroot00000000000000# 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. from yaql.language import exceptions import yaql.tests class TestQueries(yaql.tests.TestCase): def test_where(self): data = [1, 2, 3, 4, 5, 6] self.assertEqual([4, 5, 6], self.eval('$.where($ > 3)', data=data)) def test_select(self): data = [1, 2, 3] self.assertEqual([1, 4, 9], self.eval('$.select($ * $)', data=data)) def test_keyword_collection_access(self): data = [{'a': 2}, {'a': 4}] self.assertEqual([2, 4], self.eval('$.a', data=data)) self.assertEqual([2, 4], self.eval('$.select($).a', data=data)) def test_skip(self): data = [1, 2, 3, 4] self.assertEqual([2, 3, 4], self.eval('$.skip(1)', data=data)) def test_limit(self): data = [1, 2, 3, 4] self.assertEqual([1, 2], self.eval('$.limit(2)', data=data)) self.assertEqual([1, 2], self.eval('$.take(2)', data=data)) def test_append(self): data = [1, 2] self.assertEqual([1, 2, 3, 4], self.eval('$.append(3, 4)', data=data)) def test_complex_query(self): data = [1, 2, 3, 4, 5, 6] self.assertEqual( [4], self.eval('$.where($ < 4).select($ * $).skip(1).limit(1)', data=data)) def test_distinct(self): data = [1, 2, 3, 2, 4, 8] self.assertEqual([1, 2, 3, 4, 8], self.eval('$.distinct()', data=data)) self.assertEqual([1, 2, 3, 4, 8], self.eval('distinct($)', data=data)) def test_distinct_structures(self): data = [{'a': 1}, {'b': 2}, {'a': 1}] self.assertEqual( [{'a': 1}, {'b': 2}], self.eval('$.distinct()', data=data)) def test_distinct_with_selector(self): data = [['a', 1], ['b', 2], ['c', 1], ['d', 3], ['e', 2]] self.assertItemsEqual([['a', 1], ['b', 2], ['d', 3]], self.eval('$.distinct($[1])', data=data)) self.assertItemsEqual([['a', 1], ['b', 2], ['d', 3]], self.eval('distinct($, $[1])', data=data)) def test_any(self): self.assertFalse(self.eval('$.any()', data=[])) self.assertTrue(self.eval('$.any()', data=[0])) def test_all(self): self.assertTrue(self.eval('$.all()', data=[])) self.assertFalse(self.eval('$.all()', data=[1, 0])) self.assertTrue(self.eval('$.all()', data=[1, 2])) self.assertFalse(self.eval('$.all($ > 1)', data=[2, 1])) self.assertTrue(self.eval('$.all($ > 1)', data=[2, 3])) def test_enumerate(self): data = [1, 2, 3] self.assertEqual([[0, 1], [1, 2], [2, 3]], self.eval('$.enumerate()', data=data)) self.assertEqual([[3, 1], [4, 2], [5, 3]], self.eval('$.enumerate(3)', data=data)) self.assertEqual([[0, 1], [1, 2], [2, 3]], self.eval('enumerate($)', data=data)) self.assertEqual([[3, 1], [4, 2], [5, 3]], self.eval('enumerate($, 3)', data=data)) def test_concat(self): data = [1, 2, 3] self.assertEqual( [1, 2, 3, 2, 4, 6], self.eval('$.select($).concat($.select(2 * $))', data=data)) self.assertEqual( [1, 2, 3, 2, 4, 6, 1, 2, 3], self.eval('concat($, $.select(2 * $), $)', data=data)) def test_len(self): data = [1, 2, 3] self.assertEqual(3, self.eval('len($)', data=data)) self.assertEqual(3, self.eval('$.len()', data=data)) self.assertEqual(3, self.eval('$.count()', data=data)) self.assertRaises( exceptions.FunctionResolutionError, self.eval, 'count($)', data=data) def test_sum(self): data = range(4) self.assertEqual(6, self.eval('$.sum()', data=data)) self.assertEqual(106, self.eval('$.sum(100)', data=data)) self.assertEqual(100, self.eval('[].sum(100)')) def test_memorize(self): generator_func = lambda: (i for i in range(3)) self.assertRaises( TypeError, self.eval, '$.len() + $.sum()', data=generator_func()) self.assertEqual( 6, self.eval('let($.memorize()) -> $.len() + $.sum()', data=generator_func())) def test_first(self): self.assertEqual(2, self.eval('list(2, 3).first()')) self.assertEqual(4, self.eval('list(2, 3).select($ * 2).first()')) self.assertIsNone(self.eval('list().first(null)')) self.assertRaises(StopIteration, self.eval, 'list().first()') self.assertEqual(99, self.eval('list().first(99)')) def test_single(self): self.assertEqual(2, self.eval('list(2).single()')) self.assertRaises(StopIteration, self.eval, 'list().single()') self.assertRaises(StopIteration, self.eval, 'list(1, 2).single()') def test_last(self): self.assertEqual(3, self.eval('list(2, 3).last()')) self.assertEqual(6, self.eval('list(2, 3).select($ * 2).last()')) self.assertIsNone(self.eval('list().last(null)')) self.assertEqual(99, self.eval('list().last(99)')) self.assertRaises(StopIteration, self.eval, 'list().last()') def test_range(self): self.assertEqual([0, 1], self.eval('range(2)')) self.assertEqual([1, 2, 3], self.eval('range(1, 4)')) self.assertEqual([4, 3, 2], self.eval('range(4, 1, -1)')) def test_select_many(self): self.assertEqual([0, 0, 1, 0, 1, 2], self.eval('range(4).selectMany(range($))')) def test_select_many_scalar(self): # check that string is not interpreted as a sequence and that # selectMany works when selector returns scalar self.assertEqual( ['xx', 'xx'], self.eval('range(2).selectMany(xx)')) def test_order_by(self): self.assertEqual( [1, 2, 3, 4], self.eval('$.orderBy($)', data=[4, 2, 1, 3])) self.assertEqual( [4, 3, 2, 1], self.eval('$.orderByDescending($)', data=[4, 2, 1, 3])) def test_order_by_multilevel(self): self.assertEqual( [[1, 0], [1, 5], [2, 2]], self.eval( '$.orderBy($[0]).thenBy($[1])', data=[[2, 2], [1, 5], [1, 0]])) self.assertEqual( [[1, 5], [1, 0], [2, 2]], self.eval( '$.orderBy($[0]).thenByDescending($[1])', data=[[2, 2], [1, 5], [1, 0]])) self.assertEqual( [[2, 2], [1, 0], [1, 5]], self.eval( '$.orderByDescending($[0]).thenBy($[1])', data=[[2, 2], [1, 5], [1, 0]])) self.assertEqual( [[2, 2], [1, 5], [1, 0]], self.eval( '$.orderByDescending($[0]).thenByDescending($[1])', data=[[2, 2], [1, 5], [1, 0]])) def test_group_by(self): data = {'a': 1, 'b': 2, 'c': 1, 'd': 3, 'e': 2} self.assertItemsEqual( [ [1, [['a', 1], ['c', 1]]], [2, [['b', 2], ['e', 2]]], [3, [['d', 3]]] ], self.eval('$.items().orderBy($[0]).groupBy($[1])', data=data)) self.assertItemsEqual( [[1, ['a', 'c']], [2, ['b', 'e']], [3, ['d']]], self.eval('$.items().orderBy($[0]).groupBy($[1], $[0])', data=data)) self.assertItemsEqual( [[1, 'ac'], [2, 'be'], [3, 'd']], self.eval('$.items().orderBy($[0]).' 'groupBy($[1], $[0], $.sum())', data=data)) self.assertItemsEqual( [[1, ['a', 1, 'c', 1]], [2, ['b', 2, 'e', 2]], [3, ['d', 3]]], self.eval('$.items().orderBy($[0]).' 'groupBy($[1],, $.sum())', data=data)) self.assertItemsEqual( [[1, ['a', 1, 'c', 1]], [2, ['b', 2, 'e', 2]], [3, ['d', 3]]], self.eval('$.items().orderBy($[0]).' 'groupBy($[1], aggregator => $.sum())', data=data)) def test_join(self): self.assertEqual( [[2, 1], [3, 1], [3, 2], [4, 1], [4, 2], [4, 3]], self.eval('$.join($, $1 > $2, [$1, $2])', data=[1, 2, 3, 4])) self.assertEqual( [[1, 3], [1, 4], [2, 3], [2, 4]], self.eval('[1,2].join([3, 4], true, [$1, $2])')) def test_zip(self): self.assertEqual( [[1, 4], [2, 5]], self.eval('[1, 2, 3].zip([4, 5])')) self.assertEqual( [[1, 4, 6], [2, 5, 7]], self.eval('[1, 2, 3].zip([4, 5], [6, 7, 8])')) def test_zip_longest(self): self.assertEqual( [[1, 4], [2, 5], [3, None]], self.eval('[1, 2, 3].zipLongest([4, 5])')) self.assertEqual( [[1, 4, 6], [2, 5, None], [3, None, None]], self.eval('[1, 2, 3].zipLongest([4, 5], [6])')) self.assertEqual( [[1, 4], [2, 5], [3, 0]], self.eval('[1, 2, 3].zipLongest([4, 5], default => 0)')) def test_repeat(self): self.assertEqual( [None, None], self.eval('null.repeat(2)')) self.assertEqual( [1, 1, 1, 1, 1], self.eval('1.repeat().limit(5)')) def test_cycle(self): self.assertEqual( [1, 2, 1, 2, 1], self.eval('[1, 2].cycle().take(5)')) def test_take_while(self): self.assertEqual( [1, 2, 3], self.eval('[1, 2, 3, 4, 5].takeWhile($ < 4)')) def test_skip_while(self): self.assertEqual( [4, 5], self.eval('[1, 2, 3, 4, 5].skipWhile($ < 4)')) def test_index_of(self): self.assertEqual(1, self.eval('[1, 2, 3, 2, 1].indexOf(2)')) self.assertEqual(-1, self.eval('[1, 2, 3, 2, 1].indexOf(22)')) def test_last_index_of(self): self.assertEqual(3, self.eval('[1, 2, 3, 2, 1].lastIndexOf(2)')) self.assertEqual(-1, self.eval('[1, 2, 3, 2, 1].lastIndexOf(22)')) def test_index_where(self): self.assertEqual(1, self.eval('[1, 2, 3, 2, 1].indexWhere($ = 2)')) self.assertEqual(-1, self.eval('[1, 2, 3, 2, 1].indexWhere($ = 22)')) def test_last_index_where(self): self.assertEqual(3, self.eval('[1, 2, 3, 2, 1].lastIndexWhere($ = 2)')) self.assertEqual( -1, self.eval('[1, 2, 3, 2, 1].lastIndexWhere($ = 22)')) def test_slice(self): self.assertEqual( [[1, 2], [3, 4], [5]], self.eval('range(1, 6).slice(2)')) self.assertEqual( [[1, 2], [3, 4], [5]], self.eval('[1,2,3,4,5].slice(2)')) def test_split_where(self): self.assertEqual( [[], [2, 3], [5]], self.eval('range(1, 6).splitWhere($ mod 3 = 1)')) def test_split_at(self): self.assertEqual( [[1, 2], [3, 4, 5]], self.eval('range(1, 6).splitAt(2)')) def test_slice_where(self): self.assertEqual( [['a', 'a'], ['b'], ['a', 'a']], self.eval('[a,a,b,a,a].sliceWhere($ != a)')) def test_aggregate(self): self.assertEqual( 'aabaa', self.eval('[a,a,b,a,a].aggregate($1 + $2)')) self.assertRaises( TypeError, self.eval, '[].aggregate($1 + $2)') self.assertEqual( 1, self.eval('[].aggregate($1 + $2, 1)')) self.assertEqual( 'aabaa', self.eval('[a,a,b,a,a].reduce($1 + $2)')) self.assertEqual( 0, self.eval('[].reduce(max($1, $2), 0)')) def test_accumulate(self): self.assertEqual( ['a', 'aa', u'aab', 'aaba', 'aabaa'], self.eval('[a,a,b,a,a].accumulate($1 + $2)')) self.assertEqual( [1], self.eval('[].accumulate($1 + $2, 1)')) def test_default_if_empty(self): self.assertEqual( [1, 2], self.eval('[].defaultIfEmpty([1, 2])')) self.assertEqual( [3, 4], self.eval('[3, 4].defaultIfEmpty([1, 2])')) self.assertEqual( [1, 2], self.eval('[].select($).defaultIfEmpty([1, 2])')) self.assertEqual( [3, 4], self.eval('[3, 4].select($).defaultIfEmpty([1, 2])')) def test_generate(self): self.assertEqual( [0, 2, 4, 6, 8], self.eval('generate(0, $ < 10, $ + 2)')) self.assertEqual( [0, 4, 16, 36, 64], self.eval('generate(0, $ < 10, $ + 2, $ * $)')) def test_generate_many(self): friends = { 'John': ['Jim'], 'Jim': ['Jay', 'Jax'], 'Jax': ['John', 'Jacob', 'Jonathan'], 'Jacob': ['Jonathan', 'Jenifer'], } self.assertEqual( ['John', 'Jim', 'Jay', 'Jax', 'Jacob', 'Jonathan', 'Jenifer'], self.eval( 'generateMany(John, $data.get($, []), decycle => true)', friends)) self.assertEqual( ['John', 'Jim', 'Jay', 'Jax', 'Jacob', 'Jonathan', 'Jenifer'], self.eval( 'generateMany(John, $data.get($, []), ' 'decycle => true, depthFirst => true)', friends)) self.assertEqual( ['Jay'], self.eval('generateMany(Jay, $data.get($, []))', friends)) self.assertEqual( ['JAX', 'JOHN', 'JACOB', 'JONATHAN', 'JIM', 'JENIFER', 'JAY'], self.eval( 'generateMany(Jax, $data.get($, []), $.toUpper(), ' 'decycle => true)', friends)) def test_max(self): self.assertEqual( 0, self.eval('[].max(0)')) self.assertRaises( TypeError, self.eval, '[].max()') self.assertEqual( 234, self.eval('[44, 234, 23].max()')) def test_min(self): self.assertEqual( 0, self.eval('[].min(0)')) self.assertRaises( TypeError, self.eval, '[].min()') self.assertEqual( 23, self.eval('[44, 234, 23].min()')) def test_reverse(self): self.assertEqual( [9, 4, 1], self.eval('range(1, 4).select($*$).reverse()')) def test_merge_with(self): dict1 = {'a': 1, 'b': 'x', 'c': [1, 2], 'x': {'a': 1}} dict2 = {'d': 5, 'b': 'y', 'c': [2, 3], 'x': {'b': 2}} self.assertEqual( {'a': 1, 'c': [1, 2, 3], 'b': 'y', 'd': 5, 'x': {'a': 1, 'b': 2}}, self.eval( '$.d1.mergeWith($.d2)', data={'d1': dict1, 'd2': dict2})) dict1 = {'a': 1, 'b': 2, 'c': [1, 2]} dict2 = {'d': 5, 'b': 3, 'c': [2, 3]} self.assertEqual( {'a': 1, 'c': [1, 2, 2, 3], 'b': 3, 'd': 5}, self.eval( '$.d1.mergeWith($.d2, $1 + $2)', data={'d1': dict1, 'd2': dict2})) self.assertEqual( {'a': 1, 'b': 3, 'c': [2, 3], 'd': 5}, self.eval( '$.d1.mergeWith($.d2, $1 + $2, maxLevels => 1)', data={'d1': dict1, 'd2': dict2})) self.assertEqual( {'a': 1, 'b': 2, 'c': [1, 2, 3], 'd': 5}, self.eval( '$.d1.mergeWith($.d2,, min($1, $2))', data={'d1': dict1, 'd2': dict2})) def test_is_iterable(self): self.assertEqual( True, self.eval('isIterable([])')) self.assertEqual( True, self.eval('isIterable([1,2])')) self.assertEqual( True, self.eval('isIterable(set(1,2))')) self.assertEqual( False, self.eval('isIterable(1)')) self.assertEqual( False, self.eval('isIterable("foo")')) self.assertEqual( False, self.eval('isIterable({"a" => 1})')) def test_infinite_collections(self): self.assertRaises( exceptions.CollectionTooLargeException, self.eval, 'len(list(sequence()))') self.assertRaises( exceptions.CollectionTooLargeException, self.eval, 'list(sequence())') self.assertRaises( exceptions.CollectionTooLargeException, self.eval, 'len(dict(sequence().select([$, $])))') self.assertRaises( exceptions.CollectionTooLargeException, self.eval, 'dict(sequence().select([$, $]))') self.assertRaises( exceptions.CollectionTooLargeException, self.eval, 'sequence()') self.assertRaises( exceptions.CollectionTooLargeException, self.eval, 'set(sequence())') yaql-1.1.3/yaql/tests/test_regex.py000066400000000000000000000127771305545650400173420ustar00rootroot00000000000000# 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 yaql.tests class TestRegex(yaql.tests.TestCase): def test_matches(self): self.assertTrue(self.eval("regex('a.b').matches(axb)")) self.assertFalse(self.eval("regex('a.b').matches(abx)")) def test_matches_string_method(self): self.assertTrue(self.eval("axb.matches('a.b')")) self.assertFalse(self.eval("abx.matches('a.b')")) def test_matches_operator_regex(self): self.assertTrue(self.eval("axb =~ regex('a.b')")) self.assertFalse(self.eval("abx =~ regex('a.b')")) def test_not_matches_operator_regex(self): self.assertFalse(self.eval("axb !~ regex('a.b')")) self.assertTrue(self.eval("abx !~ regex('a.b')")) def test_matches_operator_string(self): self.assertTrue(self.eval("axb =~ 'a.b'")) self.assertFalse(self.eval("abx =~ 'a.b'")) def test_not_matches_operator_string(self): self.assertFalse(self.eval("axb !~ 'a.b'")) self.assertTrue(self.eval("abx !~ 'a.b'")) def test_search(self): self.assertEqual( '24.16', self.eval(r"regex(`(\d+)\.?(\d+)?`).search('a24.16b')")) def test_search_with_selector(self): self.assertEqual( '24.16 = 24(2-4) + 16(5-7)', self.eval( r"regex(`(\d+)\.?(\d+)?`).search("r"'aa24.16bb', " r"format('{0} = {1}({2}-{3}) + {4}({5}-{6})', " r"$.value, $2.value, $2.start, $2.end, " r"$3.value, $3.start, $3.end))")) def test_search_all(self): self.assertEqual( ['24', '16'], self.eval(r"regex(`\d+`).searchAll('a24.16b')")) def test_search_all_with_selector(self): self.assertEqual( ['24!', '16!'], self.eval(r"regex(`\d+`).searchAll('a24.16b', $.value+'!')")) def test_split(self): self.assertEqual( ['Words', 'words', 'words', ''], self.eval(r"regex(`\W+`).split('Words, words, words.')")) self.assertEqual( ['Words', ', ', 'words', ', ', 'words', '.', ''], self.eval(r"regex(`(\W+)`).split('Words, words, words.')")) self.assertEqual( ['Words', 'words, words.'], self.eval(r"regex(`\W+`).split('Words, words, words.', 1)")) self.assertEqual( ['0', '3', '9'], self.eval(r"regex('[a-f]+', ignoreCase => true).split('0a3B9')")) def test_split_on_string(self): self.assertEqual( ['Words', 'words', 'words', ''], self.eval(r"'Words, words, words.'.split(regex(`\W+`))")) self.assertEqual( ['Words', ', ', 'words', ', ', 'words', '.', ''], self.eval(r"'Words, words, words.'.split(regex(`(\W+)`))")) self.assertEqual( ['Words', 'words, words.'], self.eval(r"'Words, words, words.'.split(regex(`\W+`), 1)")) self.assertEqual( ['0', '3', '9'], self.eval(r"'0a3B9'.split(regex('[a-f]+', ignoreCase => true))")) def test_replace(self): self.assertEqual( 'axxbxx', self.eval(r"regex(`\d+`).replace(a12b23, xx)")) self.assertEqual( 'axxb23', self.eval(r"regex(`\d+`).replace(a12b23, xx, 1)")) def test_replace_backref(self): self.assertEqual( 'Foo_Bar_Foo', self.eval(r"regex(`([a-z0-9])([A-Z])`).replace(" "FooBarFoo, `\\1_\\2`)")) def test_replace_on_string(self): self.assertEqual( 'axxbxx', self.eval(r"a12b23.replace(regex(`\d+`), xx)")) self.assertEqual( 'axxb23', self.eval(r"a12b23.replace(regex(`\d+`), xx, 1)")) def test_replace_by(self): self.assertEqual( 'axxbyy', self.eval(r"regex(`\d+`).replaceBy(a12b23, " r"let(a => int($.value)) -> switch(" r"$a < 20 => xx, true => yy))")) self.assertEqual( 'axxb23', self.eval(r"regex(`\d+`).replaceBy(a12b23, " r"let(a => int($.value)) -> switch(" r"$a < 20 => xx, true => yy), 1)")) def test_replace_by_on_string(self): self.assertEqual( 'axxbyy', self.eval(r"a12b23.replaceBy(regex(`\d+`), " r"with(int($.value)) -> switch(" r"$ < 20 => xx, true => yy))")) self.assertEqual( 'axxb23', self.eval(r"a12b23.replaceBy(regex(`\d+`), " r"let(a => int($.value)) -> switch(" r"$a < 20 => xx, true => yy), 1)")) def test_escape_regex(self): self.assertEqual( '\\[', self.eval(r"escapeRegex('[')")) def test_is_regex(self): self.assertTrue(self.eval('isRegex(regex("a.b"))')) self.assertFalse(self.eval('isRegex(123)')) self.assertFalse(self.eval('isRegex(abc)')) yaql-1.1.3/yaql/tests/test_resolution.py000066400000000000000000000103461305545650400204210ustar00rootroot00000000000000# 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. from yaql.language import exceptions from yaql.language import specs from yaql.language import yaqltypes import yaql.tests class TestResolution(yaql.tests.TestCase): def test_resolve_parameter_count_single_layer(self): def f1(a): return a def f2(a, b): return a + b self.context.register_function(f1, name='f') self.context.register_function(f2, name='f') self.assertEqual(12, self.eval('f(12)')) self.assertEqual(25, self.eval('f(12, 13)')) def test_resolve_parameter_count_multi_layer(self): def f1(a): return a def f2(a, b): return a + b context1 = self.context.create_child_context() context1.register_function(f1, name='f') context2 = context1.create_child_context() context2.register_function(f2, name='f') self.assertEqual(12, self.eval('f(12)', context=context2)) self.assertEqual(25, self.eval('f(12, 13)', context=context2)) def test_layer_override(self): def f1(a): return a def f2(a): return -a context1 = self.context.create_child_context() context1.register_function(f1, name='f') context2 = context1.create_child_context() context2.register_function(f2, name='f') self.assertEqual(-12, self.eval('f(12)', context=context2)) def test_single_layer_ambiguity(self): def f1(a): return a def f2(a): return -a context1 = self.context.create_child_context() context1.register_function(f1, name='f') context1.register_function(f2, name='f') self.assertRaises( exceptions.AmbiguousFunctionException, self.eval, 'f(12)', context=context1) def test_single_layer_laziness_ambiguity(self): @specs.parameter('a', yaqltypes.Lambda()) def f1(a): return a() def f2(a): return -a def f3(a, b): return a + b context1 = self.context.create_child_context() context1.register_function(f1, name='f') context1.register_function(f2, name='f') context1.register_function(f3, name='f') self.assertRaises( exceptions.AmbiguousFunctionException, self.eval, 'f(2 * $)', data=3, context=context1) self.assertEqual(25, self.eval('f(12, 13)', context=context1)) def test_multi_layer_laziness_ambiguity(self): @specs.parameter('a', yaqltypes.Lambda()) def f1(a, b): return a() + b @specs.parameter('a', yaqltypes.Lambda()) def f2(a, b): return a() + b @specs.parameter('b', yaqltypes.Lambda()) def f3(a, b): return -a - b() @specs.parameter('a', yaqltypes.Lambda()) def f4(a, b): return -a() + b context1 = self.context.create_child_context() context1.register_function(f1, name='foo') context1.register_function(f2, name='bar') context2 = context1.create_child_context() context2.register_function(f3, name='foo') context2.register_function(f4, name='bar') self.assertRaises( exceptions.AmbiguousFunctionException, self.eval, 'foo(12, 13)', context=context2) self.assertEqual( 1, self.eval('bar(12, 13)', context=context2)) def test_ambiguous_method(self): self.context.register_function( lambda c, s: 1, name='select', method=True) self.assertRaises( exceptions.AmbiguousMethodException, self.eval, '[1,2].select($)') yaql-1.1.3/yaql/tests/test_strings.py000066400000000000000000000171431305545650400177110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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. from yaql.language import exceptions import yaql.tests class TestStrings(yaql.tests.TestCase): def test_scalar(self): self.assertEqual("some \ttext", self.eval("'some \\ttext'")) self.assertEqual(r"\\", self.eval(r"'\\\\'")) self.assertEqual("some \"text\"", self.eval(r'"some \"text\""')) def test_verbatim_strings(self): self.assertEqual('c:\\f\\x', self.eval(r"`c:\f\x`")) self.assertEqual('`', self.eval(r"`\``")) self.assertEqual('\\n', self.eval(r"`\n`")) self.assertEqual(r"\\", self.eval(r"`\\`")) def test_len(self): self.assertEqual(3, self.eval('len(abc)')) def test_to_upper(self): self.assertEqual('QQ', self.eval('qq.toUpper()')) self.assertEqual(u'ПРИВЕТ', self.eval(u'Привет.toUpper()')) def test_to_lower(self): self.assertEqual('qq', self.eval('QQ.toLower()')) self.assertEqual(u'привет', self.eval(u'Привет.toLower()')) def test_eq(self): self.assertTrue(self.eval('a = a')) self.assertFalse(self.eval('a = b')) def test_neq(self): self.assertFalse(self.eval('a != a')) self.assertTrue(self.eval('a != b')) def test_is_string(self): self.assertTrue(self.eval('isString(abc)')) self.assertFalse(self.eval('isString(null)')) self.assertFalse(self.eval('isString(123)')) self.assertFalse(self.eval('isString(true)')) def test_split(self): self.assertEqual( ['some', 'text'], self.eval("$.split('\\n')", data='some\ntext')) def test_rsplit(self): self.assertEqual( ['one\ntwo', 'three'], self.eval("$.rightSplit('\\n', 1)", data='one\ntwo\nthree')) def test_join(self): self.assertEqual('some-text', self.eval("[some, text].join('-')")) def test_join_pythonic(self): self.assertEqual('some-text', self.eval("'-'.join([some, text])")) def test_is_empty(self): self.assertTrue(self.eval("isEmpty('')")) self.assertTrue(self.eval("isEmpty(null)")) self.assertTrue(self.eval("null.isEmpty()")) self.assertTrue(self.eval("isEmpty(' ')")) self.assertFalse(self.eval("isEmpty(' x')")) def test_norm(self): self.assertIsNone(self.eval("norm('')")) self.assertIsNone(self.eval("norm(null)")) self.assertIsNone(self.eval("norm(' ')")) self.assertEqual('x', self.eval("norm(' x')")) def test_replace(self): self.assertEqual('AxxD', self.eval("ABBD.replace(B, x)")) self.assertEqual('AxxD', self.eval("ABxD.replace(B, x, 1)")) def test_replace_with_dict(self): self.assertEqual( 'Az1D', self.eval('AxyD.replace({x => z, y => 1})')) self.assertEqual( 'Ayfalse2D!', self.eval( "A122Dnull.replace({1 => y, 2 => false, null => '!'}, 1)")) def test_in(self): self.assertTrue(self.eval("B in ABC")) self.assertFalse(self.eval("D in ABC")) def test_str(self): self.assertEqual('null', self.eval('str(null)')) self.assertEqual('true', self.eval('str(true)')) self.assertEqual('false', self.eval('str(false)')) self.assertEqual('12', self.eval("str('12')")) def test_join_seq(self): self.assertEqual( 'text-1-null-true', self.eval("[text, 1, null, true].select(str($)).join('-')")) def test_concat_plus(self): self.assertEqual('abc', self.eval("a +b + c")) def test_concat_func(self): self.assertEqual('abc', self.eval("concat(a, b, c)")) def test_format(self): self.assertEqual('a->b', self.eval("'{0}->{x}'.format(a, x => b)")) self.assertEqual('a->b', self.eval("format('{0}->{x}', a, x => b)")) def test_trim(self): self.assertEqual('x', self.eval("' x '.trim()")) self.assertEqual('x', self.eval("'abxba'.trim(ab)")) def test_trim_left(self): self.assertEqual('x ', self.eval("' x '.trimLeft()")) self.assertEqual('xba', self.eval("'abxba'.trimLeft(ab)")) def test_trim_right(self): self.assertEqual(' x', self.eval("' x '.trimRight()")) self.assertEqual('abx', self.eval("'abxba'.trimRight(ab)")) def test_multiplication(self): self.assertEqual('xxx', self.eval("x * 3")) self.assertEqual('xxx', self.eval("3 * x")) def test_substring(self): data = 'abcdef' self.assertEqual('cdef', self.eval('$.substring(2)', data=data)) self.assertEqual('ef', self.eval('$.substring(-2)', data=data)) self.assertEqual('cde', self.eval('$.substring(2, 3)', data=data)) self.assertEqual('de', self.eval('$.substring(-3, 2)', data=data)) self.assertEqual('bcdef', self.eval('$.substring(1, -1)', data=data)) self.assertEqual('bcdef', self.eval('$.substring(-5, -1)', data=data)) def test_index_of(self): data = 'abcdefedcba' self.assertEqual(2, self.eval('$.indexOf(c)', data=data)) self.assertEqual(2, self.eval('$.indexOf(c, 2)', data=data)) self.assertEqual(-1, self.eval('$.indexOf(x)', data=data)) self.assertEqual(5, self.eval('$.indexOf(f, 3)', data=data)) self.assertEqual(7, self.eval('$.indexOf(dcb, -4, 3)', data=data)) self.assertEqual(7, self.eval('$.indexOf(dcb, -4, 100)', data=data)) self.assertEqual(-1, self.eval('$.indexOf(dcb, 0, 5)', data=data)) def test_last_index_of(self): data = 'abcdefedcbabc' self.assertEqual(12, self.eval('$.lastIndexOf(c)', data=data)) self.assertEqual(2, self.eval('$.lastIndexOf(c, 0, 4)', data=data)) self.assertEqual(-1, self.eval('$.lastIndexOf(c, 3, 4)', data=data)) self.assertEqual(12, self.eval('$.lastIndexOf(c, -1, 1)', data=data)) def test_max(self): self.assertEqual('z', self.eval('max(a, z)')) def test_min(self): self.assertEqual('a', self.eval('min(a, z)')) def test_to_char_array(self): self.assertEqual(['a', 'b', 'c'], self.eval('abc.toCharArray()')) def test_characters(self): self.assertItemsEqual( ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], self.eval('characters(octdigits => true, digits => true)')) def test_starts_with(self): self.assertTrue(self.eval("ABC.startsWith(A)")) self.assertTrue(self.eval("ABC.startsWith(B, A)")) self.assertFalse(self.eval("ABC.startsWith(C)")) self.assertRaises( exceptions.NoMatchingMethodException, self.eval, "ABC.startsWith(null)") def test_ends_with(self): self.assertTrue(self.eval("ABC.endsWith(C)")) self.assertTrue(self.eval("ABC.endsWith(B, C)")) self.assertFalse(self.eval("ABC.endsWith(B)")) self.assertRaises( exceptions.NoMatchingMethodException, self.eval, "ABC.endsWith(null)") def test_hex(self): self.assertEqual('0xff', self.eval('hex(255)')) self.assertEqual('-0x2a', self.eval('hex(-42)')) yaql-1.1.3/yaql/tests/test_system.py000066400000000000000000000152451305545650400175450ustar00rootroot00000000000000# 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. from yaql.language import exceptions from yaql.language import specs import yaql.tests class TestSystem(yaql.tests.TestCase): def test_def(self): self.assertEqual( [1, 4, 9], self.eval('def(sq, $*$) -> $.select(sq($))', data=[1, 2, 3])) self.assertEqual( [1, 4, 9], self.eval('def(sq, $arg * $arg) -> $.select(sq(arg => $))', data=[1, 2, 3])) def test_def_recursion(self): self.assertEqual(24, self.eval( 'def(rec, switch($ = 1 => 1, true => $*rec($-1))) -> rec($)', data=4)) def test_elvis_dict(self): self.assertEqual(1, self.eval('$?.a', data={'a': 1})) self.assertIsNone(self.eval('$?.a', data=None)) def test_elvis_method(self): self.assertEqual([2, 3], self.eval('$?.select($+1)', data=[1, 2])) self.assertIsNone(self.eval('$?.select($+1)', data=None)) def test_unpack(self): self.assertEqual( 5, self.eval('[2, 3].unpack() -> $1 + $2')) def test_unpack_with_names(self): self.assertEqual( 5, self.eval('[2, 3].unpack(a, b) -> $a + $b')) self.assertRaises( ValueError, self.eval, '[2, 3].unpack(a, b, c) -> $a + $b') self.assertRaises( ValueError, self.eval, '[2, 3].unpack(a) -> $a') def test_assert(self): self.assertEqual( [3, 4], self.eval('[2, 3].assert(len($) > 1).select($ + 1)')) self.assertRaises( AssertionError, self.eval, '[2].assert(len($) > 1).select($ + 1)') self.assertEqual( 3, self.eval('[2].select($ + 1).assert(len($) = 1).first()')) def test_lambda_passing(self): delegate = lambda x: x ** 2 self.assertEqual( 9, self.eval('$(3)', data=delegate)) def test_calling_non_callable(self): self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, '$(a)', data={'a': 9}) def test_function_passing(self): def func(x, y, z): return (x - y) * z context = self.context context['func'] = func self.assertEqual( 8, self.eval('$func(5, z => 2, y => 1)', data=func)) def test_lambda_expression(self): delegate = lambda x: x ** 2 self.assertEqual( 9, self.eval('$.x[0](3)', data={'x': [delegate]})) self.assertEqual( 9, self.eval('($.x[0])(3)', data={'x': [delegate]})) def test_2nd_order_lambda(self): delegate = lambda y: lambda x: x ** y self.assertEqual( 16, self.eval('$(2)(4)', data=delegate)) def test_2nd_order_lambda_expression(self): delegate = lambda y: {'key': lambda x: x ** y} self.assertEqual( 16, self.eval('$(2)[key](4)', data=delegate)) def test_2nd_order_lambda_collection_expression(self): delegate = lambda y: lambda x: y ** x self.assertEqual( [1, 8, 27], self.eval( 'let(func => $) -> [1, 2, 3].select($func($)).select($(3))', data=delegate)) def test_lambda_func(self): self.assertEqual( [2, 4, 6], self.eval('let(func => lambda(2 * $)) -> $.select($func($))', data=[1, 2, 3])) def test_lambda_func_2nd_order(self): self.assertEqual( 5, self.eval('lambda(let(outer => $) -> lambda($outer - $))(7)(2)')) def test_lambda_closure(self): data = [1, 2, 3, 4, 5, 6] self.assertEqual([3, 4, 5, 6], self.eval( '$.where(lambda($ > 3)($+1))', data=data)) # lambda can access value from "where"'s context # so we can omit parameter self.assertEqual([4, 5, 6], self.eval( '$.where(lambda($ > 3)())', data=data)) def test_properties(self): @specs.yaql_property(int) def neg_value(value): return -value self.context.register_function(neg_value) self.assertEqual(-123, self.eval('123.negValue')) self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, '"123".negValue') self.assertRaises(exceptions.NoMatchingFunctionException, self.eval, 'null.negValue') self.assertRaises(exceptions.NoFunctionRegisteredException, self.eval, '123.neg_value') def test_call_function(self): self.assertEqual( 2, self.eval('call(len, [[1,2]], {})')) self.assertEqual( 2, self.eval('call(len, [], {"sequence" => [1,2]})')) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'call(len, [[1,2]], null)') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'call(len, null, {sequence => [1,2]})') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'call(len, [], {invalid => [1,2]})') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'call(len, [1, 2], {})') self.assertTrue(self.eval('call(isEmpty, [null], {})')) def test_call_method(self): self.assertEqual( 2, self.eval('call(len, [], {}, [1,2])')) self.assertRaises( exceptions.NoMatchingMethodException, self.eval, 'call(len, [[1,2]], {}, [1,2])') self.assertRaises( exceptions.NoMatchingMethodException, self.eval, 'call(len, [], {sequence => [1,2]}, [1, 2])') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'call(len, [], null, [1, 2])') self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'call(len, null, {sequence => [1,2]}, [1, 2])') self.assertTrue(self.eval('call(isEmpty, [], {}, null)')) yaql-1.1.3/yaql/tests/test_type_aggregation.py000066400000000000000000000047731305545650400215550ustar00rootroot00000000000000# 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 six from yaql.language import exceptions from yaql.language import specs from yaql.language import yaqltypes import yaql.tests class TestTypeAggregation(yaql.tests.TestCase): def test_not_of_type(self): @specs.parameter('arg', yaqltypes.NotOfType(int)) def foo(arg): return True self.context.register_function(foo) self.assertTrue(self.eval('foo($)', data='abc')) self.assertTrue(self.eval('foo($)', data=[1, 2])) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', data=123) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', data=True) def test_chain(self): @specs.parameter( 'arg', yaqltypes.Chain(yaqltypes.NotOfType(bool), int)) def foo(arg): return True self.context.register_function(foo) self.assertTrue(self.eval('foo($)', data=123)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', data=True) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', data='abc') def test_any_of(self): @specs.parameter( 'arg', yaqltypes.AnyOf(six.string_types, yaqltypes.Integer())) def foo(arg): if isinstance(arg, six.string_types): return 1 if isinstance(arg, int): return 2 self.context.register_function(foo) self.assertEqual(1, self.eval('foo($)', data='abc')) self.assertEqual(2, self.eval('foo($)', data=123)) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', data=True) self.assertRaises( exceptions.NoMatchingFunctionException, self.eval, 'foo($)', data=[1, 2]) yaql-1.1.3/yaql/tests/test_yaql_interface.py000066400000000000000000000041111305545650400211750ustar00rootroot00000000000000# Copyright (c) 2016 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. from yaql.language import specs from yaql.language import yaqltypes import yaql.tests class TestYaqlInterface(yaql.tests.TestCase): def test_call(self): def foo(yaql_interface): return yaql_interface('2+2') @specs.inject('yi', yaqltypes.YaqlInterface()) def bar(yi): return yi('$a * $', 2, a=3) self.context.register_function(foo) self.context.register_function(bar) self.assertEqual(4, self.eval('foo()')) self.assertEqual(6, self.eval('bar()')) def test_function_call(self): def foo(yaql_interface): return yaql_interface.len([1, 2, 3]) self.context.register_function(foo) self.assertEqual(3, self.eval('foo()')) def test_method_call(self): def foo(yaql_interface): return yaql_interface.on([1, 2, 3]).where(lambda i: i > 1) @specs.inject('yi', yaqltypes.YaqlInterface()) def bar(yi): return yi.on([1, 2, 3]).select(yi.engine('$ * $')) self.context.register_function(foo) self.context.register_function(bar) self.assertEqual([2, 3], self.eval('foo()')) self.assertEqual([1, 4, 9], self.eval('bar()')) def test_data_access(self): def foo(yaql_interface): return yaql_interface[''], yaql_interface['key'] self.context.register_function(foo) self.context['key'] = 'value' self.assertEqual(['test', 'value'], self.eval('foo()', data='test')) yaql-1.1.3/yaql/tests/test_yaqlization.py000066400000000000000000000147101305545650400205610ustar00rootroot00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from yaql.language import exceptions from yaql import tests from yaql import yaqlization class TestYaqlization(tests.TestCase): def _get_sample_class(self): class D(object): d_attr = 777 class C(object): def __init__(self): self.attr = 123 def m_foo(self, arg1, arg2): return arg1 - arg2 def bar(self, string): return string.upper() def get_d(self): return D() @staticmethod def static(string): return string.upper() @classmethod def clsmethod(cls, string): return string.upper() @property def prop(self): return self.attr def __getitem__(self, item): return item return C def test_method_call_yaqlized_object(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj) self.assertEqual(3, self.eval('$.m_foo(5, 2)', obj)) self.assertEqual(3, self.eval('$.m_foo(5, arg2 => 2)', obj)) self.assertEqual(3, self.eval('$.m_foo(arg2 => 2, arg1 => 6-1)', obj)) self.assertEqual('A', self.eval('$.bar(a)', obj)) self.assertEqual('B', self.eval('$.static(b)', obj)) self.assertEqual('C', self.eval('$.clsmethod(c)', obj)) self.assertRaises( exceptions.NoFunctionRegisteredException, self.eval, 'm_foo($, 5, 2)', obj) self.assertEqual(3, self.eval('$?.m_foo(5, 2)', obj)) self.assertIsNone(self.eval('$?.m_foo(5, 2)', None)) def test_method_call_yaqlized_class(self): cls = self._get_sample_class() yaqlization.yaqlize(cls) obj = cls() self.assertEqual(3, self.eval('$.m_foo(5, 2)', obj)) def test_method_call_not_yaqlized(self): obj = self._get_sample_class()() self.assertRaises( exceptions.NoMethodRegisteredException, self.eval, '$.m_foo(5, 2)', obj) def test_method_call_forbidden(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, yaqlize_methods=False) self.assertRaises( exceptions.NoMethodRegisteredException, self.eval, '$.m_foo(5, 2)', obj) def test_property_access(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj) self.assertEqual(123, self.eval('$.attr', obj)) self.assertEqual(123, self.eval('$.prop', obj)) self.assertEqual(123, self.eval('$?.prop', obj)) self.assertIsNone(self.eval('$?.prop', None)) self.assertRaises(AttributeError, self.eval, '$.invalid', obj) def test_indexation(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj) self.assertEqual('key', self.eval('$[key]', obj)) def test_method_call_whitelist_string(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, whitelist=['m_foo']) self.assertEqual(3, self.eval('$.m_foo(5, 2)', obj)) self.assertRaises(AttributeError, self.eval, '$.bar(a)', obj) def test_method_call_whitelist_regexp(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, whitelist=[re.compile('^m_*')]) self.assertEqual(3, self.eval('$.m_foo(5, 2)', obj)) self.assertRaises(AttributeError, self.eval, '$.bar(a)', obj) def test_method_call_whitelist_callable(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, whitelist=[lambda t: t.startswith('m_')]) self.assertEqual(3, self.eval('$.m_foo(5, 2)', obj)) self.assertRaises(AttributeError, self.eval, '$.bar(a)', obj) def test_method_call_blacklist_string(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, blacklist=['m_foo']) self.assertRaises(AttributeError, self.eval, '$.m_foo(5, 2)', obj) self.assertEqual('A', self.eval('$.bar(a)', obj)) def test_method_call_blacklist_regexp(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, blacklist=[re.compile('^m_*')]) self.assertRaises(AttributeError, self.eval, '$.m_foo(5, 2)', obj) self.assertEqual('A', self.eval('$.bar(a)', obj)) def test_method_call_blacklist_callable(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, blacklist=[lambda t: t.startswith('m_')]) self.assertRaises(AttributeError, self.eval, '$.m_foo(5, 2)', obj) self.assertEqual('A', self.eval('$.bar(a)', obj)) def test_property_access_blacklist(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, blacklist=['prop']) self.assertEqual(123, self.eval('$.attr', obj)) self.assertRaises(AttributeError, self.eval, '$.prop', obj) def test_indexation_blacklist(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj, blacklist=[lambda t: t.startswith('_')]) self.assertEqual('key', self.eval('$[key]', obj)) self.assertRaises(KeyError, self.eval, '$[_key]', obj) def test_auto_yaqlization(self): obj = self._get_sample_class()() yaqlization.yaqlize(obj) self.assertRaises( exceptions.NoFunctionRegisteredException, self.eval, '$.get_d().d_attr', obj) obj = self._get_sample_class()() yaqlization.yaqlize(obj, auto_yaqlize_result=True) self.assertEqual(777, self.eval('$.get_d().d_attr', obj)) def test_yaqlify_decorator(self): @yaqlization.yaqlize class C(object): attr = 555 self.assertEqual(555, self.eval('$.attr', C())) def test_yaqlify_decorator_with_parameters(self): @yaqlization.yaqlize(yaqlize_attributes=True) class C(object): attr = 555 self.assertEqual(555, self.eval('$.attr', C())) yaql-1.1.3/yaql/yaql_interface.py000066400000000000000000000044621305545650400170050ustar00rootroot00000000000000# Copyright (c) 2016 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 six from yaql.language import utils class YaqlInterface(object): def __init__(self, context, engine, receiver=utils.NO_VALUE): self.__sender = receiver self.__engine = engine self.__context = context @property def context(self): return self.__context @property def engine(self): return self.__engine @property def sender(self): return self.__sender def on(self, receiver): return YaqlInterface(self.context, self.engine, receiver) def __getattr__(self, item): def stub(*args, **kwargs): context = self.context args = utils.convert_input_data(args) kwargs = utils.convert_input_data(kwargs) limit_func = context('#iter', self.engine) return utils.convert_output_data( context(item, self.engine, self.sender)(*args, **kwargs), limit_func, self.engine) return stub def __call__(self, __expression, *args, **kwargs): context = self.context.create_child_context() args = utils.convert_input_data(args) for i, arg_value in enumerate(args): context['$' + str(i + 1)] = arg_value kwargs = utils.convert_input_data(kwargs) for arg_name, arg_value in six.iteritems(kwargs): context['$' + arg_name] = arg_value parsed = self.engine(__expression) res = parsed.evaluate(context=context) limit_func = context('#iter', self.engine) return utils.convert_output_data(res, limit_func, self.engine) def __getitem__(self, item): return self.context[item] def __setitem__(self, key, value): self.context[key] = value yaql-1.1.3/yaql/yaqlization.py000066400000000000000000000052331305545650400163600ustar00rootroot00000000000000# Copyright (c) 2016 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 six YAQLIZATION_ATTR = '__yaqlization__' def yaqlize(class_or_object=None, yaqlize_attributes=True, yaqlize_methods=True, yaqlize_indexer=True, auto_yaqlize_result=False, whitelist=None, blacklist=None, attribute_remapping=None, blacklist_remapped_attributes=True): def func(something): if not hasattr(something, YAQLIZATION_ATTR): setattr(something, YAQLIZATION_ATTR, build_yaqlization_settings( yaqlize_attributes=yaqlize_attributes, yaqlize_methods=yaqlize_methods, yaqlize_indexer=yaqlize_indexer, auto_yaqlize_result=auto_yaqlize_result, whitelist=whitelist, blacklist=blacklist, attribute_remapping=attribute_remapping, )) return something if class_or_object is None: return func else: return func(class_or_object) def get_yaqlization_settings(class_or_object): return getattr(class_or_object, YAQLIZATION_ATTR, None) def is_yaqlized(class_or_object): return hasattr(class_or_object, YAQLIZATION_ATTR) def build_yaqlization_settings( yaqlize_attributes=True, yaqlize_methods=True, yaqlize_indexer=True, auto_yaqlize_result=False, whitelist=None, blacklist=None, attribute_remapping=None, blacklist_remapped_attributes=True): whitelist = set(whitelist or []) blacklist = set(blacklist or []) attribute_remapping = attribute_remapping or {} if blacklist_remapped_attributes: for value in six.itervalues(attribute_remapping): if not isinstance(value, six.string_types): name = value[0] else: name = value blacklist.add(name) return { 'yaqlizeAttributes': yaqlize_attributes, 'yaqlizeMethods': yaqlize_methods, 'yaqlizeIndexer': yaqlize_indexer, 'autoYaqlizeResult': auto_yaqlize_result, 'whitelist': whitelist, 'blacklist': blacklist, 'attributeRemapping': attribute_remapping }