[Python-modules-commits] [python-jmespath] 01/01: New upstream version 0.9.1

Takaki Taniguchi takaki at moszumanska.debian.org
Thu Feb 2 11:14:34 UTC 2017


This is an automated email from the git hooks/post-receive script.

takaki pushed a commit to branch upstream
in repository python-jmespath.

commit a926a90528e3afb76eb77df2fc5dcb834c8a6472
Author: TANIGUCHI Takaki <takaki at asis.media-as.org>
Date:   Thu Feb 2 19:55:47 2017 +0900

    New upstream version 0.9.1
---
 PKG-INFO                   |  99 +++++++++++++++++++++++++++++++--
 README.rst                 |  95 ++++++++++++++++++++++++++++++-
 jmespath.egg-info/PKG-INFO |  99 +++++++++++++++++++++++++++++++--
 jmespath/__init__.py       |   2 +-
 jmespath/compat.py         |   9 +++
 jmespath/functions.py      | 135 +++++++++++++++++++++------------------------
 jmespath/lexer.py          |  35 ++++++++++--
 jmespath/parser.py         | 105 +++++++++++++++--------------------
 jmespath/visitor.py        |  66 ++++++++++++++++------
 setup.py                   |   3 +-
 tests/test_compliance.py   |  47 ++++++++++------
 11 files changed, 508 insertions(+), 187 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 418337f..dbbbb75 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,11 +1,11 @@
 Metadata-Version: 1.1
 Name: jmespath
-Version: 0.9.0
+Version: 0.9.1
 Summary: JSON Matching Expressions
 Home-page: https://github.com/jmespath/jmespath.py
 Author: James Saryerwinnie
 Author-email: js at jamesls.com
-License: UNKNOWN
+License: MIT
 Description: JMESPath
         ========
         
@@ -18,6 +18,10 @@ Description: JMESPath
            :target: http://travis-ci.org/jmespath/jmespath.py
         
         
+        .. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop
+            :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop
+        
+        
         JMESPath (pronounced "james path") allows you to declaratively specify how to
         extract elements from a JSON document.
         
@@ -56,7 +60,9 @@ Description: JMESPath
         
         The ``jmespath.py`` library has two functions
         that operate on python data structures.  You can use ``search``
-        and give it the jmespath expression and the data::
+        and give it the jmespath expression and the data:
+        
+        .. code:: python
         
             >>> import jmespath
             >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}})
@@ -64,7 +70,9 @@ Description: JMESPath
         
         Similar to the ``re`` module, you can use the ``compile`` function
         to compile the JMESPath expression and use this parsed expression
-        to perform repeated searches::
+        to perform repeated searches:
+        
+        .. code:: python
         
             >>> import jmespath
             >>> expression = jmespath.compile('foo.bar')
@@ -83,7 +91,9 @@ Description: JMESPath
         You can provide an instance of ``jmespath.Options`` to control how
         a JMESPath expression is evaluated.  The most common scenario for
         using an ``Options`` instance is if you want to have ordered output
-        of your dict keys.  To do this you can use either of these options::
+        of your dict keys.  To do this you can use either of these options:
+        
+        .. code:: python
         
             >>> import jmespath
             >>> jmespath.search('{a: a, b: b},
@@ -98,6 +108,85 @@ Description: JMESPath
             ...               jmespath.Options(dict_cls=collections.OrderedDict))
         
         
+        Custom Functions
+        ~~~~~~~~~~~~~~~~
+        
+        The JMESPath language has numerous
+        `built-in functions
+        <http://jmespath.org/specification.html#built-in-functions>`__, but it is
+        also possible to add your own custom functions.  Keep in mind that
+        custom function support in jmespath.py is experimental and the API may
+        change based on feedback.
+        
+        **If you have a custom function that you've found useful, consider submitting
+        it to jmespath.site and propose that it be added to the JMESPath language.**
+        You can submit proposals
+        `here <https://github.com/jmespath/jmespath.site/issues>`__.
+        
+        To create custom functions:
+        
+        * Create a subclass of ``jmespath.functions.Functions``.
+        * Create a method with the name ``_func_<your function name>``.
+        * Apply the ``jmespath.functions.signature`` decorator that indicates
+          the expected types of the function arguments.
+        * Provide an instance of your subclass in a ``jmespath.Options`` object.
+        
+        Below are a few examples:
+        
+        .. code:: python
+        
+            import jmespath
+            from jmespath import functions
+        
+            # 1. Create a subclass of functions.Functions.
+            #    The function.Functions base class has logic
+            #    that introspects all of its methods and automatically
+            #    registers your custom functions in its function table.
+            class CustomFunctions(functions.Functions):
+        
+                # 2 and 3.  Create a function that starts with _func_
+                # and decorate it with @signature which indicates its
+                # expected types.
+                # In this example, we're creating a jmespath function
+                # called "unique_letters" that accepts a single argument
+                # with an expected type "string".
+                @functions.signature({'types': ['string']})
+                def _func_unique_letters(self, s):
+                    # Given a string s, return a sorted
+                    # string of unique letters: 'ccbbadd' ->  'abcd'
+                    return ''.join(sorted(set(s)))
+        
+                # Here's another example.  This is creating
+                # a jmespath function called "my_add" that expects
+                # two arguments, both of which should be of type number.
+                @functions.signature({'types': ['number']}, {'types': ['number']})
+                def _func_my_add(self, x, y):
+                    return x + y
+        
+            # 4. Provide an instance of your subclass in a Options object.
+            options = jmespath.Options(custom_functions=CustomFunctions())
+        
+            # Provide this value to jmespath.search:
+            # This will print 3
+            print(
+                jmespath.search(
+                    'my_add(`1`, `2`)', {}, options=options)
+            )
+        
+            # This will print "abcd"
+            print(
+                jmespath.search(
+                    'foo.bar | unique_letters(@)',
+                    {'foo': {'bar': 'ccbbadd'}},
+                    options=options)
+            )
+        
+        Again, if you come up with useful functions that you think make
+        sense in the JMESPath language (and make sense to implement in all
+        JMESPath libraries, not just python), please let us know at
+        `jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__.
+        
+        
         Specification
         =============
         
diff --git a/README.rst b/README.rst
index 76844bd..cf0524c 100644
--- a/README.rst
+++ b/README.rst
@@ -10,6 +10,10 @@ JMESPath
    :target: http://travis-ci.org/jmespath/jmespath.py
 
 
+.. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop
+    :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop
+
+
 JMESPath (pronounced "james path") allows you to declaratively specify how to
 extract elements from a JSON document.
 
@@ -48,7 +52,9 @@ API
 
 The ``jmespath.py`` library has two functions
 that operate on python data structures.  You can use ``search``
-and give it the jmespath expression and the data::
+and give it the jmespath expression and the data:
+
+.. code:: python
 
     >>> import jmespath
     >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}})
@@ -56,7 +62,9 @@ and give it the jmespath expression and the data::
 
 Similar to the ``re`` module, you can use the ``compile`` function
 to compile the JMESPath expression and use this parsed expression
-to perform repeated searches::
+to perform repeated searches:
+
+.. code:: python
 
     >>> import jmespath
     >>> expression = jmespath.compile('foo.bar')
@@ -75,7 +83,9 @@ Options
 You can provide an instance of ``jmespath.Options`` to control how
 a JMESPath expression is evaluated.  The most common scenario for
 using an ``Options`` instance is if you want to have ordered output
-of your dict keys.  To do this you can use either of these options::
+of your dict keys.  To do this you can use either of these options:
+
+.. code:: python
 
     >>> import jmespath
     >>> jmespath.search('{a: a, b: b},
@@ -90,6 +100,85 @@ of your dict keys.  To do this you can use either of these options::
     ...               jmespath.Options(dict_cls=collections.OrderedDict))
 
 
+Custom Functions
+~~~~~~~~~~~~~~~~
+
+The JMESPath language has numerous
+`built-in functions
+<http://jmespath.org/specification.html#built-in-functions>`__, but it is
+also possible to add your own custom functions.  Keep in mind that
+custom function support in jmespath.py is experimental and the API may
+change based on feedback.
+
+**If you have a custom function that you've found useful, consider submitting
+it to jmespath.site and propose that it be added to the JMESPath language.**
+You can submit proposals
+`here <https://github.com/jmespath/jmespath.site/issues>`__.
+
+To create custom functions:
+
+* Create a subclass of ``jmespath.functions.Functions``.
+* Create a method with the name ``_func_<your function name>``.
+* Apply the ``jmespath.functions.signature`` decorator that indicates
+  the expected types of the function arguments.
+* Provide an instance of your subclass in a ``jmespath.Options`` object.
+
+Below are a few examples:
+
+.. code:: python
+
+    import jmespath
+    from jmespath import functions
+
+    # 1. Create a subclass of functions.Functions.
+    #    The function.Functions base class has logic
+    #    that introspects all of its methods and automatically
+    #    registers your custom functions in its function table.
+    class CustomFunctions(functions.Functions):
+
+        # 2 and 3.  Create a function that starts with _func_
+        # and decorate it with @signature which indicates its
+        # expected types.
+        # In this example, we're creating a jmespath function
+        # called "unique_letters" that accepts a single argument
+        # with an expected type "string".
+        @functions.signature({'types': ['string']})
+        def _func_unique_letters(self, s):
+            # Given a string s, return a sorted
+            # string of unique letters: 'ccbbadd' ->  'abcd'
+            return ''.join(sorted(set(s)))
+
+        # Here's another example.  This is creating
+        # a jmespath function called "my_add" that expects
+        # two arguments, both of which should be of type number.
+        @functions.signature({'types': ['number']}, {'types': ['number']})
+        def _func_my_add(self, x, y):
+            return x + y
+
+    # 4. Provide an instance of your subclass in a Options object.
+    options = jmespath.Options(custom_functions=CustomFunctions())
+
+    # Provide this value to jmespath.search:
+    # This will print 3
+    print(
+        jmespath.search(
+            'my_add(`1`, `2`)', {}, options=options)
+    )
+
+    # This will print "abcd"
+    print(
+        jmespath.search(
+            'foo.bar | unique_letters(@)',
+            {'foo': {'bar': 'ccbbadd'}},
+            options=options)
+    )
+
+Again, if you come up with useful functions that you think make
+sense in the JMESPath language (and make sense to implement in all
+JMESPath libraries, not just python), please let us know at
+`jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__.
+
+
 Specification
 =============
 
diff --git a/jmespath.egg-info/PKG-INFO b/jmespath.egg-info/PKG-INFO
index 418337f..dbbbb75 100644
--- a/jmespath.egg-info/PKG-INFO
+++ b/jmespath.egg-info/PKG-INFO
@@ -1,11 +1,11 @@
 Metadata-Version: 1.1
 Name: jmespath
-Version: 0.9.0
+Version: 0.9.1
 Summary: JSON Matching Expressions
 Home-page: https://github.com/jmespath/jmespath.py
 Author: James Saryerwinnie
 Author-email: js at jamesls.com
-License: UNKNOWN
+License: MIT
 Description: JMESPath
         ========
         
@@ -18,6 +18,10 @@ Description: JMESPath
            :target: http://travis-ci.org/jmespath/jmespath.py
         
         
+        .. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop
+            :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop
+        
+        
         JMESPath (pronounced "james path") allows you to declaratively specify how to
         extract elements from a JSON document.
         
@@ -56,7 +60,9 @@ Description: JMESPath
         
         The ``jmespath.py`` library has two functions
         that operate on python data structures.  You can use ``search``
-        and give it the jmespath expression and the data::
+        and give it the jmespath expression and the data:
+        
+        .. code:: python
         
             >>> import jmespath
             >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}})
@@ -64,7 +70,9 @@ Description: JMESPath
         
         Similar to the ``re`` module, you can use the ``compile`` function
         to compile the JMESPath expression and use this parsed expression
-        to perform repeated searches::
+        to perform repeated searches:
+        
+        .. code:: python
         
             >>> import jmespath
             >>> expression = jmespath.compile('foo.bar')
@@ -83,7 +91,9 @@ Description: JMESPath
         You can provide an instance of ``jmespath.Options`` to control how
         a JMESPath expression is evaluated.  The most common scenario for
         using an ``Options`` instance is if you want to have ordered output
-        of your dict keys.  To do this you can use either of these options::
+        of your dict keys.  To do this you can use either of these options:
+        
+        .. code:: python
         
             >>> import jmespath
             >>> jmespath.search('{a: a, b: b},
@@ -98,6 +108,85 @@ Description: JMESPath
             ...               jmespath.Options(dict_cls=collections.OrderedDict))
         
         
+        Custom Functions
+        ~~~~~~~~~~~~~~~~
+        
+        The JMESPath language has numerous
+        `built-in functions
+        <http://jmespath.org/specification.html#built-in-functions>`__, but it is
+        also possible to add your own custom functions.  Keep in mind that
+        custom function support in jmespath.py is experimental and the API may
+        change based on feedback.
+        
+        **If you have a custom function that you've found useful, consider submitting
+        it to jmespath.site and propose that it be added to the JMESPath language.**
+        You can submit proposals
+        `here <https://github.com/jmespath/jmespath.site/issues>`__.
+        
+        To create custom functions:
+        
+        * Create a subclass of ``jmespath.functions.Functions``.
+        * Create a method with the name ``_func_<your function name>``.
+        * Apply the ``jmespath.functions.signature`` decorator that indicates
+          the expected types of the function arguments.
+        * Provide an instance of your subclass in a ``jmespath.Options`` object.
+        
+        Below are a few examples:
+        
+        .. code:: python
+        
+            import jmespath
+            from jmespath import functions
+        
+            # 1. Create a subclass of functions.Functions.
+            #    The function.Functions base class has logic
+            #    that introspects all of its methods and automatically
+            #    registers your custom functions in its function table.
+            class CustomFunctions(functions.Functions):
+        
+                # 2 and 3.  Create a function that starts with _func_
+                # and decorate it with @signature which indicates its
+                # expected types.
+                # In this example, we're creating a jmespath function
+                # called "unique_letters" that accepts a single argument
+                # with an expected type "string".
+                @functions.signature({'types': ['string']})
+                def _func_unique_letters(self, s):
+                    # Given a string s, return a sorted
+                    # string of unique letters: 'ccbbadd' ->  'abcd'
+                    return ''.join(sorted(set(s)))
+        
+                # Here's another example.  This is creating
+                # a jmespath function called "my_add" that expects
+                # two arguments, both of which should be of type number.
+                @functions.signature({'types': ['number']}, {'types': ['number']})
+                def _func_my_add(self, x, y):
+                    return x + y
+        
+            # 4. Provide an instance of your subclass in a Options object.
+            options = jmespath.Options(custom_functions=CustomFunctions())
+        
+            # Provide this value to jmespath.search:
+            # This will print 3
+            print(
+                jmespath.search(
+                    'my_add(`1`, `2`)', {}, options=options)
+            )
+        
+            # This will print "abcd"
+            print(
+                jmespath.search(
+                    'foo.bar | unique_letters(@)',
+                    {'foo': {'bar': 'ccbbadd'}},
+                    options=options)
+            )
+        
+        Again, if you come up with useful functions that you think make
+        sense in the JMESPath language (and make sense to implement in all
+        JMESPath libraries, not just python), please let us know at
+        `jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__.
+        
+        
         Specification
         =============
         
diff --git a/jmespath/__init__.py b/jmespath/__init__.py
index fdd3c79..69d67fb 100644
--- a/jmespath/__init__.py
+++ b/jmespath/__init__.py
@@ -1,7 +1,7 @@
 from jmespath import parser
 from jmespath.visitor import Options
 
-__version__ = '0.9.0'
+__version__ = '0.9.1'
 
 
 def compile(expression):
diff --git a/jmespath/compat.py b/jmespath/compat.py
index 7b70adb..2ed0fe7 100644
--- a/jmespath/compat.py
+++ b/jmespath/compat.py
@@ -3,6 +3,15 @@ import inspect
 
 PY2 = sys.version_info[0] == 2
 
+
+def with_metaclass(meta, *bases):
+    # Taken from flask/six.
+    class metaclass(meta):
+        def __new__(cls, name, this_bases, d):
+            return meta(name, bases, d)
+    return type.__new__(metaclass, 'temporary_class', (), {})
+
+
 if PY2:
     text_type = unicode
     string_type = basestring
diff --git a/jmespath/functions.py b/jmespath/functions.py
index e306f7b..ab92282 100644
--- a/jmespath/functions.py
+++ b/jmespath/functions.py
@@ -1,10 +1,9 @@
 import math
 import json
-import weakref
 
 from jmespath import exceptions
 from jmespath.compat import string_type as STRING_TYPE
-from jmespath.compat import get_methods
+from jmespath.compat import get_methods, with_metaclass
 
 
 # python types -> jmespath types
@@ -35,48 +34,39 @@ REVERSE_TYPES_MAP = {
 }
 
 
-def populate_function_table(cls):
-    func_table = cls.FUNCTION_TABLE
-    for name, method in get_methods(cls):
-        signature = getattr(method, 'signature', None)
-        if signature is not None:
-            func_table[name[6:]] = {"function": method,
-                                    "signature": signature}
-    return cls
-
-
-def builtin_function(*arguments):
-    def _record_arity(func):
+def signature(*arguments):
+    def _record_signature(func):
         func.signature = arguments
         return func
-    return _record_arity
+    return _record_signature
 
 
- at populate_function_table
-class RuntimeFunctions(object):
-    # The built in functions are automatically populated in the FUNCTION_TABLE
-    # using the @builtin_function decorator on methods defined in this class.
+class FunctionRegistry(type):
+    def __init__(cls, name, bases, attrs):
+        cls._populate_function_table()
+        super(FunctionRegistry, cls).__init__(name, bases, attrs)
 
-    FUNCTION_TABLE = {
-    }
+    def _populate_function_table(cls):
+        function_table = getattr(cls, 'FUNCTION_TABLE', {})
+        # Any method with a @signature decorator that also
+        # starts with "_func_" is registered as a function.
+        # _func_max_by -> max_by function.
+        for name, method in get_methods(cls):
+            if not name.startswith('_func_'):
+                continue
+            signature = getattr(method, 'signature', None)
+            if signature is not None:
+                function_table[name[6:]] = {
+                    'function': method,
+                    'signature': signature,
+                }
+        cls.FUNCTION_TABLE = function_table
 
-    def __init__(self):
-        self._interpreter = None
 
-    @property
-    def interpreter(self):
-        if self._interpreter is None:
-            return None
-        else:
-            return self._interpreter()
+class Functions(with_metaclass(FunctionRegistry, object)):
 
-    @interpreter.setter
-    def interpreter(self, value):
-        # A weakref is used because we have
-        # a cyclic reference and we want to allow
-        # for the memory to be properly freed when
-        # the objects are no longer needed.
-        self._interpreter = weakref.ref(value)
+    FUNCTION_TABLE = {
+    }
 
     def call_function(self, function_name, resolved_args):
         try:
@@ -170,28 +160,31 @@ class RuntimeFunctions(object):
                     raise exceptions.JMESPathTypeError(
                         function_name, element, actual_typename, types)
 
-    @builtin_function({'types': ['number']})
+    @signature({'types': ['number']})
     def _func_abs(self, arg):
         return abs(arg)
 
-    @builtin_function({'types': ['array-number']})
+    @signature({'types': ['array-number']})
     def _func_avg(self, arg):
-        return sum(arg) / float(len(arg))
+        if arg:
+            return sum(arg) / float(len(arg))
+        else:
+            return None
 
-    @builtin_function({'types': [], 'variadic': True})
+    @signature({'types': [], 'variadic': True})
     def _func_not_null(self, *arguments):
         for argument in arguments:
             if argument is not None:
                 return argument
 
-    @builtin_function({'types': []})
+    @signature({'types': []})
     def _func_to_array(self, arg):
         if isinstance(arg, list):
             return arg
         else:
             return [arg]
 
-    @builtin_function({'types': []})
+    @signature({'types': []})
     def _func_to_string(self, arg):
         if isinstance(arg, STRING_TYPE):
             return arg
@@ -199,7 +192,7 @@ class RuntimeFunctions(object):
             return json.dumps(arg, separators=(',', ':'),
                               default=str)
 
-    @builtin_function({'types': []})
+    @signature({'types': []})
     def _func_to_number(self, arg):
         if isinstance(arg, (list, dict, bool)):
             return None
@@ -216,88 +209,88 @@ class RuntimeFunctions(object):
             except ValueError:
                 return None
 
-    @builtin_function({'types': ['array', 'string']}, {'types': []})
+    @signature({'types': ['array', 'string']}, {'types': []})
     def _func_contains(self, subject, search):
         return search in subject
 
-    @builtin_function({'types': ['string', 'array', 'object']})
+    @signature({'types': ['string', 'array', 'object']})
     def _func_length(self, arg):
         return len(arg)
 
-    @builtin_function({'types': ['string']}, {'types': ['string']})
+    @signature({'types': ['string']}, {'types': ['string']})
     def _func_ends_with(self, search, suffix):
         return search.endswith(suffix)
 
-    @builtin_function({'types': ['string']}, {'types': ['string']})
+    @signature({'types': ['string']}, {'types': ['string']})
     def _func_starts_with(self, search, suffix):
         return search.startswith(suffix)
 
-    @builtin_function({'types': ['array', 'string']})
+    @signature({'types': ['array', 'string']})
     def _func_reverse(self, arg):
         if isinstance(arg, STRING_TYPE):
             return arg[::-1]
         else:
             return list(reversed(arg))
 
-    @builtin_function({"types": ['number']})
+    @signature({"types": ['number']})
     def _func_ceil(self, arg):
         return math.ceil(arg)
 
-    @builtin_function({"types": ['number']})
+    @signature({"types": ['number']})
     def _func_floor(self, arg):
         return math.floor(arg)
 
-    @builtin_function({"types": ['string']}, {"types": ['array-string']})
+    @signature({"types": ['string']}, {"types": ['array-string']})
     def _func_join(self, separator, array):
         return separator.join(array)
 
-    @builtin_function({'types': ['expref']}, {'types': ['array']})
+    @signature({'types': ['expref']}, {'types': ['array']})
     def _func_map(self, expref, arg):
         result = []
         for element in arg:
-            result.append(self.interpreter.visit(expref.expression, element))
+            result.append(expref.visit(expref.expression, element))
         return result
 
-    @builtin_function({"types": ['array-number', 'array-string']})
+    @signature({"types": ['array-number', 'array-string']})
     def _func_max(self, arg):
         if arg:
             return max(arg)
         else:
             return None
 
-    @builtin_function({"types": ["object"], "variadic": True})
+    @signature({"types": ["object"], "variadic": True})
     def _func_merge(self, *arguments):
         merged = {}
         for arg in arguments:
             merged.update(arg)
         return merged
 
-    @builtin_function({"types": ['array-number', 'array-string']})
+    @signature({"types": ['array-number', 'array-string']})
     def _func_min(self, arg):
         if arg:
             return min(arg)
         else:
             return None
 
-    @builtin_function({"types": ['array-string', 'array-number']})
+    @signature({"types": ['array-string', 'array-number']})
     def _func_sort(self, arg):
         return list(sorted(arg))
 
-    @builtin_function({"types": ['array-number']})
+    @signature({"types": ['array-number']})
     def _func_sum(self, arg):
         return sum(arg)
 
-    @builtin_function({"types": ['object']})
+    @signature({"types": ['object']})
     def _func_keys(self, arg):
         # To be consistent with .values()
         # should we also return the indices of a list?
         return list(arg.keys())
 
-    @builtin_function({"types": ['object']})
+    @signature({"types": ['object']})
     def _func_values(self, arg):
         return list(arg.values())
 
-    @builtin_function({'types': []})
+    @signature({'types': []})
     def _func_type(self, arg):
         if isinstance(arg, STRING_TYPE):
             return "string"
@@ -312,7 +305,7 @@ class RuntimeFunctions(object):
         elif arg is None:
             return "null"
 
-    @builtin_function({'types': ['array']}, {'types': ['expref']})
+    @signature({'types': ['array']}, {'types': ['expref']})
     def _func_sort_by(self, array, expref):
         if not array:
             return array
@@ -323,34 +316,32 @@ class RuntimeFunctions(object):
         # that validates that type, which requires that remaining array
         # elements resolve to the same type as the first element.
         required_type = self._convert_to_jmespath_type(
-            type(self.interpreter.visit(expref.expression, array[0])).__name__)
+            type(expref.visit(expref.expression, array[0])).__name__)
         if required_type not in ['number', 'string']:
             raise exceptions.JMESPathTypeError(
                 'sort_by', array[0], required_type, ['string', 'number'])
-        keyfunc = self._create_key_func(expref.expression,
+        keyfunc = self._create_key_func(expref,
                                         [required_type],
                                         'sort_by')
         return list(sorted(array, key=keyfunc))
 
-    @builtin_function({'types': ['array']}, {'types': ['expref']})
+    @signature({'types': ['array']}, {'types': ['expref']})
     def _func_min_by(self, array, expref):
-        keyfunc = self._create_key_func(expref.expression,
+        keyfunc = self._create_key_func(expref,
                                         ['number', 'string'],
                                         'min_by')
         return min(array, key=keyfunc)
 
-    @builtin_function({'types': ['array']}, {'types': ['expref']})
+    @signature({'types': ['array']}, {'types': ['expref']})
     def _func_max_by(self, array, expref):
-        keyfunc = self._create_key_func(expref.expression,
+        keyfunc = self._create_key_func(expref,
                                         ['number', 'string'],
                                         'min_by')
         return max(array, key=keyfunc)
 
-    def _create_key_func(self, expr_node, allowed_types, function_name):
-        interpreter = self.interpreter
-
+    def _create_key_func(self, expref, allowed_types, function_name):
         def keyfunc(x):
-            result = interpreter.visit(expr_node, x)
+            result = expref.visit(expref.expression, x)
             actual_typename = type(result).__name__
             jmespath_type = self._convert_to_jmespath_type(actual_typename)
             # allowed_types is in term of jmespath types, not python types.
diff --git a/jmespath/lexer.py b/jmespath/lexer.py
index 8458bea..457a372 100644
--- a/jmespath/lexer.py
+++ b/jmespath/lexer.py
@@ -8,7 +8,6 @@ from jmespath.exceptions import LexerError, EmptyExpressionError
 class Lexer(object):
     START_IDENTIFIER = set(string.ascii_letters + '_')
     VALID_IDENTIFIER = set(string.ascii_letters + string.digits + '_')
-    START_NUMBER = set(string.digits + '-')
     VALID_NUMBER = set(string.digits)
     WHITESPACE = set(" \t\n\r")
     SIMPLE_TOKENS = {
@@ -63,13 +62,22 @@ class Lexer(object):
                 yield self._match_or_else('&', 'and', 'expref')
             elif self._current == '`':
                 yield self._consume_literal()
-            elif self._current in self.START_NUMBER:
+            elif self._current in self.VALID_NUMBER:
                 start = self._position
-                buff = self._current
-                while self._next() in self.VALID_NUMBER:
-                    buff += self._current
+                buff = self._consume_number()
                 yield {'type': 'number', 'value': int(buff),
                        'start': start, 'end': start + len(buff)}
+            elif self._current == '-':
+                # Negative number.
+                start = self._position
+                buff = self._consume_number()
+                if len(buff) > 1:
+                    yield {'type': 'number', 'value': int(buff),
+                           'start': start, 'end': start + len(buff)}
+                else:
+                    raise LexerError(lexer_position=start,
+                                     lexer_value=buff,
+                                     message="Unknown token '%s'" % buff)
             elif self._current == '"':
                 yield self._consume_quoted_identifier()
             elif self._current == '<':
@@ -79,7 +87,15 @@ class Lexer(object):
             elif self._current == '!':
                 yield self._match_or_else('=', 'ne', 'not')
             elif self._current == '=':
-                yield self._match_or_else('=', 'eq', 'unknown')
+                if self._next() == '=':
+                    yield {'type': 'eq', 'value': '==',
+                        'start': self._position - 1, 'end': self._position}
+                    self._next()
+                else:
+                    raise LexerError(
+                        lexer_position=self._position - 1,
+                        lexer_value='=',
+                        message="Unknown token =")
             else:
                 raise LexerError(lexer_position=self._position,
                                  lexer_value=self._current,
@@ -87,6 +103,13 @@ class Lexer(object):
         yield {'type': 'eof', 'value': '',
                'start': self._length, 'end': self._length}
 
+    def _consume_number(self):
+        start = self._position
+        buff = self._current
+        while self._next() in self.VALID_NUMBER:
+            buff += self._current
+        return buff
+
     def _initialize_for_expression(self, expression):
         if not expression:
             raise EmptyExpressionError()
diff --git a/jmespath/parser.py b/jmespath/parser.py
index 233d575..4d5ba38 100644
--- a/jmespath/parser.py
+++ b/jmespath/parser.py
@@ -39,6 +39,7 @@ class Parser(object):
         'eof': 0,
         'unquoted_identifier': 0,
         'quoted_identifier': 0,
+        'literal': 0,
         'rbracket': 0,
         'rparen': 0,
         'comma': 0,
@@ -133,9 +134,6 @@ class Parser(object):
                 current_token = self._current_token()
         return left
 
-    def _token_nud_string_literal(self, token):
-        return ast.literal(token['value'])
-
     def _token_nud_literal(self, token):
         return ast.literal(token['value'])
 
@@ -224,17 +222,16 @@ class Parser(object):
         while not current_token == 'rbracket' and index < 3:
             if current_token == 'colon':
                 index += 1
+                if index == 3:
+                    self._raise_parse_error_for_token(
+                        self._lookahead_token(0), 'syntax error')
                 self._advance()
             elif current_token == 'number':
                 parts[index] = self._lookahead_token(0)['value']
                 self._advance()
             else:
-                t = self._lookahead_token(0)
-                lex_position = t['start']
-                actual_value = t['value']
-                actual_type = t['type']
-                raise exceptions.ParseError(lex_position, actual_value,
-                                            actual_type, 'syntax error')
+                self._raise_parse_error_for_token(
+                    self._lookahead_token(0), 'syntax error')
             current_token = self._current_token()
         self._match('rbracket')
         return ast.slice(*parts)
@@ -274,6 +271,14 @@ class Parser(object):
         return ast.and_expression(left, right)
 
     def _token_led_lparen(self, left):
+        if left['type'] != 'field':
+            #  0 - first func arg or closing paren.
+            # -1 - '(' token
+            # -2 - invalid function "name".
+            prev_t = self._lookahead_token(-2)
+            raise exceptions.ParseError(
+                prev_t['start'], prev_t['value'], prev_t['type'],
+                "Invalid function name '%s'" % prev_t['value'])
         name = left['value']
         args = []
         while not self._current_token() == 'rparen':
@@ -396,12 +401,8 @@ class Parser(object):
             self._match('dot')
             right = self._parse_dot_rhs(binding_power)
         else:
-            t = self._lookahead_token(0)
-            lex_position = t['start']
-            actual_value = t['value']
-            actual_type = t['type']
-            raise exceptions.ParseError(lex_position, actual_value,
-                                        actual_type, 'syntax error')
+            self._raise_parse_error_for_token(self._lookahead_token(0),
+                                              'syntax error')
         return right
 
     def _parse_dot_rhs(self, binding_power):
@@ -427,34 +428,19 @@ class Parser(object):
             t = self._lookahead_token(0)
             allowed = ['quoted_identifier', 'unquoted_identifier',
                        'lbracket', 'lbrace']
-            lex_position = t['start']
-            actual_value = t['value']
-            actual_type = t['type']
-            raise exceptions.ParseError(
-                lex_position, actual_value, actual_type,
-                "Expecting: %s, got: %s" % (allowed,
-                                            actual_type))
-
-    def _assert_not_token(self, *token_types):
-        if self._current_token() in token_types:
-            t = self._lookahead_token(0)
-            lex_position = t['start']
-            actual_value = t['value']
-            actual_type = t['type']
-            raise exceptions.ParseError(
-                lex_position, actual_value, actual_type,
-                "Token %s not allowed to be: %s" % (actual_type, token_types))
+            msg = (
+                "Expecting: %s, got: %s" % (allowed, t['type'])
+            )
+            self._raise_parse_error_for_token(t, msg)
 
     def _error_nud_token(self, token):
         if token['type'] == 'eof':
             raise exceptions.IncompleteExpressionError(
                 token['start'], token['value'], token['type'])
-        raise exceptions.ParseError(token['start'], token['value'],
-                                    token['type'], 'Invalid token.')
+        self._raise_parse_error_for_token(token, 'invalid token')
 
     def _error_led_token(self, token):
-        raise exceptions.ParseError(token['start'], token['value'],
-                                    token['type'], 'Invalid token')
+        self._raise_parse_error_for_token(token, 'invalid token')
 
     def _match(self, token_type=None):
         # inline'd self._current_token()
@@ -462,33 +448,13 @@ class Parser(object):
             # inline'd self._advance()
             self._advance()
         else:
-            t = self._lookahead_token(0)
-            lex_position = t['start']
-            actual_value = t['value']
-            actual_type = t['type']
-            if actual_type == 'eof':
-                raise exceptions.IncompleteExpressionError(
-                    lex_position, actual_value, actual_type)
-            else:
-                message = 'Expecting: %s, got: %s' % (token_type,
-                                                      actual_type)
-            raise exceptions.ParseError(
-                lex_position, actual_value, actual_type, message)
+            self._raise_parse_error_maybe_eof(
+                token_type, self._lookahead_token(0))
 
     def _match_multiple_tokens(self, token_types):
         if self._current_token() not in token_types:
-            t = self._lookahead_token(0)
-            lex_position = t['start']
-            actual_value = t['value']
-            actual_type = t['type']
-            if actual_type == 'eof':
-                raise exceptions.IncompleteExpressionError(
-                    lex_position, actual_value, actual_type)
-            else:
-                message = 'Expecting: %s, got: %s' % (token_types,
-                                                      actual_type)
-            raise exceptions.ParseError(
-                lex_position, actual_value, actual_type, message)
+            self._raise_parse_error_maybe_eof(
+                token_types, self._lookahead_token(0))
         self._advance()
... 255 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-jmespath.git



More information about the Python-modules-commits mailing list