omlish 0.0.0.dev117__py3-none-any.whl → 0.0.0.dev119__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +2 -2
- omlish/collections/hasheq.py +0 -10
- omlish/fnpairs.py +1 -10
- omlish/formats/json/__init__.py +5 -0
- omlish/formats/json/literals.py +179 -0
- omlish/formats/json/render.py +2 -3
- omlish/formats/json/stream/render.py +1 -1
- omlish/formats/json/types.py +6 -0
- omlish/lang/classes/abstract.py +37 -14
- omlish/lang/imports.py +1 -1
- omlish/lang/maybes.py +10 -12
- omlish/lite/journald.py +163 -0
- omlish/lite/logs.py +6 -2
- omlish/logs/_abc.py +53 -0
- omlish/logs/handlers.py +1 -1
- omlish/specs/jmespath/ast.py +199 -60
- omlish/specs/jmespath/cli.py +43 -29
- omlish/specs/jmespath/functions.py +397 -274
- omlish/specs/jmespath/lexer.py +2 -2
- omlish/specs/jmespath/parser.py +169 -133
- omlish/specs/jmespath/scope.py +15 -11
- omlish/specs/jmespath/visitor.py +211 -137
- omlish/testing/pytest/plugins/pydevd.py +6 -6
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/RECORD +29 -26
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/top_level.txt +0 -0
    
        omlish/specs/jmespath/scope.py
    CHANGED
    
    | @@ -1,14 +1,18 @@ | |
| 1 | 
            -
            import  | 
| 1 | 
            +
            import typing as ta
         | 
| 2 2 |  | 
| 3 3 |  | 
| 4 | 
            -
             | 
| 4 | 
            +
            K = ta.TypeVar('K')
         | 
| 5 | 
            +
            V = ta.TypeVar('V')
         | 
| 6 | 
            +
             | 
| 7 | 
            +
             | 
| 8 | 
            +
            class ScopedChainDict(ta.Generic[K, V]):
         | 
| 5 9 | 
             
                """
         | 
| 6 10 | 
             
                Dictionary that can delegate lookups to multiple dicts. This provides a basic get/set dict interface that is backed
         | 
| 7 11 | 
             
                by multiple dicts.  Each dict is searched from the top most (most recently pushed) scope dict until a match is
         | 
| 8 12 | 
             
                found.
         | 
| 9 13 | 
             
                """
         | 
| 10 14 |  | 
| 11 | 
            -
                def __init__(self, *scopes):
         | 
| 15 | 
            +
                def __init__(self, *scopes: ta.Mapping[K, V]) -> None:
         | 
| 12 16 | 
             
                    super().__init__()
         | 
| 13 17 |  | 
| 14 18 | 
             
                    # The scopes are evaluated starting at the top of the stack (the most recently pushed scope via .push_scope()).
         | 
| @@ -16,22 +20,22 @@ class ScopedChainDict: | |
| 16 20 | 
             
                    # call reversed(self._scopes) whenever we resolve a key, because the end of the list is the top of the stack.
         | 
| 17 21 | 
             
                    # To avoid this, we're using a deque so we can append to the front of the list via .appendleft() in constant
         | 
| 18 22 | 
             
                    # time, and iterate over scopes without having to do so with a reversed() call each time.
         | 
| 19 | 
            -
                    self._scopes =  | 
| 23 | 
            +
                    self._scopes: list[ta.Mapping[K, V]] = list(reversed(scopes))
         | 
| 20 24 |  | 
| 21 | 
            -
                def __getitem__(self, key):
         | 
| 22 | 
            -
                    for scope in self._scopes:
         | 
| 25 | 
            +
                def __getitem__(self, key: K) -> V:
         | 
| 26 | 
            +
                    for scope in reversed(self._scopes):
         | 
| 23 27 | 
             
                        if key in scope:
         | 
| 24 28 | 
             
                            return scope[key]
         | 
| 25 29 | 
             
                    raise KeyError(key)
         | 
| 26 30 |  | 
| 27 | 
            -
                def get(self, key, default=None):
         | 
| 31 | 
            +
                def get(self, key: K, default: V | None = None) -> V | None:
         | 
| 28 32 | 
             
                    try:
         | 
| 29 33 | 
             
                        return self[key]
         | 
| 30 34 | 
             
                    except KeyError:
         | 
| 31 35 | 
             
                        return default
         | 
| 32 36 |  | 
| 33 | 
            -
                def push_scope(self, scope):
         | 
| 34 | 
            -
                    self._scopes. | 
| 37 | 
            +
                def push_scope(self, scope: ta.Mapping[K, V]) -> None:
         | 
| 38 | 
            +
                    self._scopes.append(scope)
         | 
| 35 39 |  | 
| 36 | 
            -
                def pop_scope(self):
         | 
| 37 | 
            -
                    self._scopes. | 
| 40 | 
            +
                def pop_scope(self) -> None:
         | 
| 41 | 
            +
                    self._scopes.pop()
         | 
    
        omlish/specs/jmespath/visitor.py
    CHANGED
    
    | @@ -2,12 +2,54 @@ import numbers | |
| 2 2 | 
             
            import operator
         | 
| 3 3 | 
             
            import typing as ta
         | 
| 4 4 |  | 
| 5 | 
            -
            from  | 
| 6 | 
            -
            from  | 
| 5 | 
            +
            from ... import check
         | 
| 6 | 
            +
            from ... import lang
         | 
| 7 | 
            +
            from .ast import AndExpression
         | 
| 8 | 
            +
            from .ast import Arithmetic
         | 
| 9 | 
            +
            from .ast import ArithmeticUnary
         | 
| 10 | 
            +
            from .ast import Assign
         | 
| 11 | 
            +
            from .ast import Comparator
         | 
| 12 | 
            +
            from .ast import CurrentNode
         | 
| 13 | 
            +
            from .ast import Expref
         | 
| 14 | 
            +
            from .ast import Field
         | 
| 15 | 
            +
            from .ast import FilterProjection
         | 
| 16 | 
            +
            from .ast import Flatten
         | 
| 17 | 
            +
            from .ast import FunctionExpression
         | 
| 18 | 
            +
            from .ast import Identity
         | 
| 19 | 
            +
            from .ast import Index
         | 
| 20 | 
            +
            from .ast import IndexExpression
         | 
| 21 | 
            +
            from .ast import KeyValPair
         | 
| 22 | 
            +
            from .ast import LetExpression
         | 
| 23 | 
            +
            from .ast import Literal
         | 
| 24 | 
            +
            from .ast import MultiSelectDict
         | 
| 25 | 
            +
            from .ast import MultiSelectList
         | 
| 7 26 | 
             
            from .ast import Node
         | 
| 27 | 
            +
            from .ast import NotExpression
         | 
| 28 | 
            +
            from .ast import OrExpression
         | 
| 29 | 
            +
            from .ast import Pipe
         | 
| 30 | 
            +
            from .ast import Projection
         | 
| 31 | 
            +
            from .ast import RootNode
         | 
| 32 | 
            +
            from .ast import Slice
         | 
| 33 | 
            +
            from .ast import Subexpression
         | 
| 34 | 
            +
            from .ast import ValueProjection
         | 
| 35 | 
            +
            from .ast import VariableRef
         | 
| 36 | 
            +
            from .exceptions import UndefinedVariableError
         | 
| 37 | 
            +
            from .functions import DefaultFunctions
         | 
| 38 | 
            +
            from .functions import Functions
         | 
| 8 39 | 
             
            from .scope import ScopedChainDict
         | 
| 9 40 |  | 
| 10 41 |  | 
| 42 | 
            +
            if ta.TYPE_CHECKING:
         | 
| 43 | 
            +
                import html
         | 
| 44 | 
            +
                import json
         | 
| 45 | 
            +
            else:
         | 
| 46 | 
            +
                html = lang.proxy_import('html')
         | 
| 47 | 
            +
                json = lang.proxy_import('json')
         | 
| 48 | 
            +
             | 
| 49 | 
            +
             | 
| 50 | 
            +
            ##
         | 
| 51 | 
            +
             | 
| 52 | 
            +
             | 
| 11 53 | 
             
            def _equals(x: ta.Any, y: ta.Any) -> bool:
         | 
| 12 54 | 
             
                if _is_special_number_case(x, y):
         | 
| 13 55 | 
             
                    return False
         | 
| @@ -58,58 +100,69 @@ def _is_actual_number(x: ta.Any) -> bool: | |
| 58 100 | 
             
                return isinstance(x, numbers.Number)
         | 
| 59 101 |  | 
| 60 102 |  | 
| 103 | 
            +
            def node_type(n: Node) -> str:
         | 
| 104 | 
            +
                return lang.snake_case(type(n).__name__)
         | 
| 105 | 
            +
             | 
| 106 | 
            +
             | 
| 107 | 
            +
            ##
         | 
| 108 | 
            +
             | 
| 109 | 
            +
             | 
| 110 | 
            +
            class Visitor:
         | 
| 111 | 
            +
                def __init__(self) -> None:
         | 
| 112 | 
            +
                    super().__init__()
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    self._method_cache: dict[str, ta.Callable] = {}
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
         | 
| 117 | 
            +
                    nty = node_type(node)
         | 
| 118 | 
            +
                    method = self._method_cache.get(nty)
         | 
| 119 | 
            +
                    if method is None:
         | 
| 120 | 
            +
                        method = check.not_none(getattr(self, f'visit_{nty}', self.default_visit))
         | 
| 121 | 
            +
                        self._method_cache[nty] = method
         | 
| 122 | 
            +
                    return method(node, *args, **kwargs)
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                def default_visit(self, node, *args, **kwargs):
         | 
| 125 | 
            +
                    raise NotImplementedError('default_visit')
         | 
| 126 | 
            +
             | 
| 127 | 
            +
             | 
| 128 | 
            +
            ##
         | 
| 129 | 
            +
             | 
| 130 | 
            +
             | 
| 61 131 | 
             
            class Options:
         | 
| 62 132 | 
             
                """Options to control how a Jmespath function is evaluated."""
         | 
| 63 133 |  | 
| 64 134 | 
             
                def __init__(
         | 
| 65 135 | 
             
                        self,
         | 
| 66 136 | 
             
                        dict_cls: type | None = None,
         | 
| 67 | 
            -
                        custom_functions=None,
         | 
| 137 | 
            +
                        custom_functions: Functions | None = None,
         | 
| 68 138 | 
             
                        enable_legacy_literals: bool = False,
         | 
| 69 139 | 
             
                ) -> None:
         | 
| 70 140 | 
             
                    super().__init__()
         | 
| 71 141 |  | 
| 72 | 
            -
                     | 
| 73 | 
            -
                    # | 
| 74 | 
            -
                    # | 
| 75 | 
            -
                    # | 
| 142 | 
            +
                    # The class to use when creating a dict.  The interpreter may create dictionaries during the evaluation of a
         | 
| 143 | 
            +
                    # Jmespath expression.  For example, a multi-select hash will create a dictionary.  By default we use a dict()
         | 
| 144 | 
            +
                    # type. You can set this value to change what dict type is used. The most common reason you would change this is
         | 
| 145 | 
            +
                    # if you want to set a collections.OrderedDict so that you can have predictable key ordering.
         | 
| 76 146 | 
             
                    self.dict_cls = dict_cls
         | 
| 77 147 | 
             
                    self.custom_functions = custom_functions
         | 
| 78 148 |  | 
| 79 | 
            -
                     | 
| 80 | 
            -
                    # | 
| 81 | 
            -
                    # | 
| 82 | 
            -
                    # | 
| 149 | 
            +
                    # The flag to enable pre-JEP-12 literal compatibility.
         | 
| 150 | 
            +
                    # JEP-12 deprecates `foo` -> "foo" syntax.
         | 
| 151 | 
            +
                    # Valid expressions MUST use: `"foo"` -> "foo"
         | 
| 152 | 
            +
                    # Setting this flag to `True` enables support for legacy syntax.
         | 
| 83 153 | 
             
                    self.enable_legacy_literals = enable_legacy_literals
         | 
| 84 154 |  | 
| 85 155 |  | 
| 86 156 | 
             
            class _Expression:
         | 
| 87 | 
            -
                def __init__(self, expression, interpreter):
         | 
| 157 | 
            +
                def __init__(self, expression: Node, interpreter: 'TreeInterpreter') -> None:
         | 
| 88 158 | 
             
                    super().__init__()
         | 
| 89 159 | 
             
                    self.expression = expression
         | 
| 90 160 | 
             
                    self.interpreter = interpreter
         | 
| 91 161 |  | 
| 92 | 
            -
                def visit(self, node, *args, **kwargs):
         | 
| 162 | 
            +
                def visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
         | 
| 93 163 | 
             
                    return self.interpreter.visit(node, *args, **kwargs)
         | 
| 94 164 |  | 
| 95 165 |  | 
| 96 | 
            -
            class Visitor:
         | 
| 97 | 
            -
                def __init__(self):
         | 
| 98 | 
            -
                    super().__init__()
         | 
| 99 | 
            -
                    self._method_cache = {}
         | 
| 100 | 
            -
             | 
| 101 | 
            -
                def visit(self, node, *args, **kwargs):
         | 
| 102 | 
            -
                    node_type = node['type']
         | 
| 103 | 
            -
                    method = self._method_cache.get(node_type)
         | 
| 104 | 
            -
                    if method is None:
         | 
| 105 | 
            -
                        method = getattr(self, f'visit_{node["type"]}', self.default_visit)
         | 
| 106 | 
            -
                        self._method_cache[node_type] = method
         | 
| 107 | 
            -
                    return method(node, *args, **kwargs)  # type: ignore
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                def default_visit(self, node, *args, **kwargs):
         | 
| 110 | 
            -
                    raise NotImplementedError('default_visit')
         | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 166 | 
             
            class TreeInterpreter(Visitor):
         | 
| 114 167 | 
             
                COMPARATOR_FUNC: ta.Mapping[str, ta.Callable] = {
         | 
| 115 168 | 
             
                    'eq': _equals,
         | 
| @@ -120,7 +173,10 @@ class TreeInterpreter(Visitor): | |
| 120 173 | 
             
                    'gte': operator.ge,
         | 
| 121 174 | 
             
                }
         | 
| 122 175 |  | 
| 123 | 
            -
                _EQUALITY_OPS: ta. | 
| 176 | 
            +
                _EQUALITY_OPS: ta.AbstractSet[str] = {
         | 
| 177 | 
            +
                    'eq',
         | 
| 178 | 
            +
                    'ne',
         | 
| 179 | 
            +
                }
         | 
| 124 180 |  | 
| 125 181 | 
             
                _ARITHMETIC_UNARY_FUNC: ta.Mapping[str, ta.Callable] = {
         | 
| 126 182 | 
             
                    'minus': operator.neg,
         | 
| @@ -136,115 +192,113 @@ class TreeInterpreter(Visitor): | |
| 136 192 | 
             
                    'plus': operator.add,
         | 
| 137 193 | 
             
                }
         | 
| 138 194 |  | 
| 139 | 
            -
                 | 
| 140 | 
            -
             | 
| 141 | 
            -
                def __init__(self, options=None):
         | 
| 195 | 
            +
                def __init__(self, options: Options | None = None) -> None:
         | 
| 142 196 | 
             
                    super().__init__()
         | 
| 143 197 |  | 
| 144 | 
            -
                    self._dict_cls =  | 
| 198 | 
            +
                    self._dict_cls: type = dict
         | 
| 145 199 |  | 
| 146 200 | 
             
                    if options is None:
         | 
| 147 201 | 
             
                        options = Options()
         | 
| 148 202 | 
             
                    self._options = options
         | 
| 149 203 |  | 
| 150 204 | 
             
                    if options.dict_cls is not None:
         | 
| 151 | 
            -
                        self._dict_cls =  | 
| 205 | 
            +
                        self._dict_cls = options.dict_cls
         | 
| 152 206 |  | 
| 153 207 | 
             
                    if options.custom_functions is not None:
         | 
| 154 | 
            -
                        self._functions =  | 
| 208 | 
            +
                        self._functions: Functions = options.custom_functions
         | 
| 155 209 | 
             
                    else:
         | 
| 156 | 
            -
                        self._functions =  | 
| 210 | 
            +
                        self._functions = DefaultFunctions()
         | 
| 157 211 |  | 
| 158 | 
            -
                    self._root = None
         | 
| 159 | 
            -
                    self._scope = ScopedChainDict()
         | 
| 212 | 
            +
                    self._root: Node | None = None
         | 
| 213 | 
            +
                    self._scope: ScopedChainDict = ScopedChainDict()
         | 
| 160 214 |  | 
| 161 | 
            -
                def default_visit(self, node, *args, **kwargs):
         | 
| 162 | 
            -
                    raise NotImplementedError(node | 
| 215 | 
            +
                def default_visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
         | 
| 216 | 
            +
                    raise NotImplementedError(node_type(node))
         | 
| 163 217 |  | 
| 164 | 
            -
                def evaluate(self, ast, root: Node) -> ta.Any:
         | 
| 218 | 
            +
                def evaluate(self, ast: Node, root: Node) -> ta.Any:
         | 
| 165 219 | 
             
                    self._root = root
         | 
| 166 220 | 
             
                    return self.visit(ast, root)
         | 
| 167 221 |  | 
| 168 | 
            -
                def visit_subexpression(self, node, value):
         | 
| 222 | 
            +
                def visit_subexpression(self, node: Subexpression, value: ta.Any) -> ta.Any:
         | 
| 169 223 | 
             
                    result = value
         | 
| 170 | 
            -
                    for child in node | 
| 224 | 
            +
                    for child in node.children_nodes:
         | 
| 171 225 | 
             
                        result = self.visit(child, result)
         | 
| 172 226 | 
             
                        if result is None:
         | 
| 173 227 | 
             
                            return None
         | 
| 174 228 | 
             
                    return result
         | 
| 175 229 |  | 
| 176 | 
            -
                def visit_field(self, node, value):
         | 
| 230 | 
            +
                def visit_field(self, node: Field, value: ta.Any) -> ta.Any:
         | 
| 177 231 | 
             
                    try:
         | 
| 178 | 
            -
                        return value.get(node | 
| 232 | 
            +
                        return value.get(node.name)
         | 
| 179 233 | 
             
                    except AttributeError:
         | 
| 180 234 | 
             
                        return None
         | 
| 181 235 |  | 
| 182 | 
            -
                def visit_comparator(self, node, value):
         | 
| 236 | 
            +
                def visit_comparator(self, node: Comparator, value: ta.Any) -> ta.Any:
         | 
| 183 237 | 
             
                    # Common case: comparator is == or !=
         | 
| 184 | 
            -
                    comparator_func = self.COMPARATOR_FUNC[node | 
| 185 | 
            -
                    if node | 
| 238 | 
            +
                    comparator_func = self.COMPARATOR_FUNC[node.name]
         | 
| 239 | 
            +
                    if node.name in self._EQUALITY_OPS:
         | 
| 186 240 | 
             
                        return comparator_func(
         | 
| 187 | 
            -
                            self.visit(node | 
| 188 | 
            -
                            self.visit(node | 
| 241 | 
            +
                            self.visit(node.first, value),
         | 
| 242 | 
            +
                            self.visit(node.second, value),
         | 
| 189 243 | 
             
                        )
         | 
| 190 244 |  | 
| 191 245 | 
             
                    else:
         | 
| 192 246 | 
             
                        # Ordering operators are only valid for numbers. Evaluating any other type with a comparison operator will
         | 
| 193 247 | 
             
                        # yield a None value.
         | 
| 194 | 
            -
                        left = self.visit(node | 
| 195 | 
            -
                        right = self.visit(node | 
| 248 | 
            +
                        left = self.visit(node.first, value)
         | 
| 249 | 
            +
                        right = self.visit(node.second, value)
         | 
| 196 250 | 
             
                        # num_types = (int, float)
         | 
| 197 251 | 
             
                        if not (_is_comparable(left) and _is_comparable(right)):
         | 
| 198 252 | 
             
                            return None
         | 
| 199 253 | 
             
                        return comparator_func(left, right)
         | 
| 200 254 |  | 
| 201 | 
            -
                def visit_arithmetic_unary(self, node, value):
         | 
| 202 | 
            -
                    operation = self._ARITHMETIC_UNARY_FUNC[node | 
| 255 | 
            +
                def visit_arithmetic_unary(self, node: ArithmeticUnary, value: ta.Any) -> ta.Any:
         | 
| 256 | 
            +
                    operation = self._ARITHMETIC_UNARY_FUNC[node.operator]
         | 
| 203 257 | 
             
                    return operation(
         | 
| 204 | 
            -
                        self.visit(node | 
| 258 | 
            +
                        self.visit(node.expression, value),
         | 
| 205 259 | 
             
                    )
         | 
| 206 260 |  | 
| 207 | 
            -
                def visit_arithmetic(self, node, value):
         | 
| 208 | 
            -
                    operation = self._ARITHMETIC_FUNC[node | 
| 261 | 
            +
                def visit_arithmetic(self, node: Arithmetic, value: ta.Any) -> ta.Any:
         | 
| 262 | 
            +
                    operation = self._ARITHMETIC_FUNC[node.operator]
         | 
| 209 263 | 
             
                    return operation(
         | 
| 210 | 
            -
                        self.visit(node | 
| 211 | 
            -
                        self.visit(node | 
| 264 | 
            +
                        self.visit(node.left, value),
         | 
| 265 | 
            +
                        self.visit(node.right, value),
         | 
| 212 266 | 
             
                    )
         | 
| 213 267 |  | 
| 214 | 
            -
                def  | 
| 268 | 
            +
                def visit_current_node(self, node: CurrentNode, value: ta.Any) -> ta.Any:
         | 
| 215 269 | 
             
                    return value
         | 
| 216 270 |  | 
| 217 | 
            -
                def  | 
| 271 | 
            +
                def visit_root_node(self, node: RootNode, value: ta.Any) -> ta.Any:
         | 
| 218 272 | 
             
                    return self._root
         | 
| 219 273 |  | 
| 220 | 
            -
                def visit_expref(self, node, value):
         | 
| 221 | 
            -
                    return _Expression(node | 
| 274 | 
            +
                def visit_expref(self, node: Expref, value: ta.Any) -> ta.Any:
         | 
| 275 | 
            +
                    return _Expression(node.expression, self)
         | 
| 222 276 |  | 
| 223 | 
            -
                def visit_function_expression(self, node, value):
         | 
| 277 | 
            +
                def visit_function_expression(self, node: FunctionExpression, value: ta.Any) -> ta.Any:
         | 
| 224 278 | 
             
                    resolved_args = []
         | 
| 225 | 
            -
                    for child in node | 
| 279 | 
            +
                    for child in node.args:
         | 
| 226 280 | 
             
                        current = self.visit(child, value)
         | 
| 227 281 | 
             
                        resolved_args.append(current)
         | 
| 228 282 |  | 
| 229 | 
            -
                    return self._functions.call_function(node | 
| 283 | 
            +
                    return self._functions.call_function(node.name, resolved_args)
         | 
| 230 284 |  | 
| 231 | 
            -
                def visit_filter_projection(self, node, value):
         | 
| 232 | 
            -
                    base = self.visit(node | 
| 285 | 
            +
                def visit_filter_projection(self, node: FilterProjection, value: ta.Any) -> ta.Any:
         | 
| 286 | 
            +
                    base = self.visit(node.left, value)
         | 
| 233 287 | 
             
                    if not isinstance(base, list):
         | 
| 234 288 | 
             
                        return None
         | 
| 235 289 |  | 
| 236 | 
            -
                    comparator_node = node | 
| 290 | 
            +
                    comparator_node = node.comparator
         | 
| 237 291 | 
             
                    collected = []
         | 
| 238 292 | 
             
                    for element in base:
         | 
| 239 293 | 
             
                        if self._is_true(self.visit(comparator_node, element)):
         | 
| 240 | 
            -
                            current = self.visit(node | 
| 294 | 
            +
                            current = self.visit(node.right, element)
         | 
| 241 295 | 
             
                            if current is not None:
         | 
| 242 296 | 
             
                                collected.append(current)
         | 
| 243 297 |  | 
| 244 298 | 
             
                    return collected
         | 
| 245 299 |  | 
| 246 | 
            -
                def visit_flatten(self, node, value):
         | 
| 247 | 
            -
                    base = self.visit(node | 
| 300 | 
            +
                def visit_flatten(self, node: Flatten, value: ta.Any) -> ta.Any:
         | 
| 301 | 
            +
                    base = self.visit(node.node, value)
         | 
| 248 302 | 
             
                    if not isinstance(base, list):
         | 
| 249 303 | 
             
                        # Can't flatten the object if it's not a list.
         | 
| 250 304 | 
             
                        return None
         | 
| @@ -258,77 +312,74 @@ class TreeInterpreter(Visitor): | |
| 258 312 |  | 
| 259 313 | 
             
                    return merged_list
         | 
| 260 314 |  | 
| 261 | 
            -
                def visit_identity(self, node, value):
         | 
| 315 | 
            +
                def visit_identity(self, node: Identity, value: ta.Any) -> ta.Any:
         | 
| 262 316 | 
             
                    return value
         | 
| 263 317 |  | 
| 264 | 
            -
                def visit_index(self, node, value):
         | 
| 318 | 
            +
                def visit_index(self, node: Index, value: ta.Any) -> ta.Any:
         | 
| 265 319 | 
             
                    # Even though we can index strings, we don't want to support that.
         | 
| 266 320 | 
             
                    if not isinstance(value, list):
         | 
| 267 321 | 
             
                        return None
         | 
| 268 322 |  | 
| 269 323 | 
             
                    try:
         | 
| 270 | 
            -
                        return value[node | 
| 324 | 
            +
                        return value[node.index]
         | 
| 271 325 | 
             
                    except IndexError:
         | 
| 272 326 | 
             
                        return None
         | 
| 273 327 |  | 
| 274 | 
            -
                def visit_index_expression(self, node, value):
         | 
| 328 | 
            +
                def visit_index_expression(self, node: IndexExpression, value: ta.Any) -> ta.Any:
         | 
| 275 329 | 
             
                    result = value
         | 
| 276 | 
            -
                    for child in node | 
| 330 | 
            +
                    for child in node.nodes:
         | 
| 277 331 | 
             
                        result = self.visit(child, result)
         | 
| 278 332 |  | 
| 279 333 | 
             
                    return result
         | 
| 280 334 |  | 
| 281 | 
            -
                def visit_slice(self, node, value):
         | 
| 335 | 
            +
                def visit_slice(self, node: Slice, value: ta.Any) -> ta.Any:
         | 
| 282 336 | 
             
                    if isinstance(value, str):
         | 
| 283 | 
            -
                         | 
| 284 | 
            -
                        end = node['children'][1]
         | 
| 285 | 
            -
                        step = node['children'][2]
         | 
| 286 | 
            -
                        return value[start:end:step]
         | 
| 337 | 
            +
                        return value[node.start:node.end:node.step]
         | 
| 287 338 |  | 
| 288 339 | 
             
                    if not isinstance(value, list):
         | 
| 289 340 | 
             
                        return None
         | 
| 290 341 |  | 
| 291 | 
            -
                    s = slice( | 
| 342 | 
            +
                    s = slice(node.start, node.end, node.step)
         | 
| 292 343 | 
             
                    return value[s]
         | 
| 293 344 |  | 
| 294 | 
            -
                def visit_key_val_pair(self, node, value):
         | 
| 295 | 
            -
                    return self.visit(node | 
| 345 | 
            +
                def visit_key_val_pair(self, node: KeyValPair, value: ta.Any) -> ta.Any:
         | 
| 346 | 
            +
                    return self.visit(node.node, value)
         | 
| 296 347 |  | 
| 297 | 
            -
                def visit_literal(self, node, value):
         | 
| 298 | 
            -
                    return node | 
| 348 | 
            +
                def visit_literal(self, node: Literal, value: ta.Any):
         | 
| 349 | 
            +
                    return node.literal_value
         | 
| 299 350 |  | 
| 300 | 
            -
                def visit_multi_select_dict(self, node, value):
         | 
| 351 | 
            +
                def visit_multi_select_dict(self, node: MultiSelectDict, value: ta.Any) -> ta.Any:
         | 
| 301 352 | 
             
                    collected = self._dict_cls()
         | 
| 302 | 
            -
                    for child in node | 
| 303 | 
            -
                        collected[child | 
| 353 | 
            +
                    for child in node.nodes:
         | 
| 354 | 
            +
                        collected[child.key_name] = self.visit(child, value)
         | 
| 304 355 |  | 
| 305 356 | 
             
                    return collected
         | 
| 306 357 |  | 
| 307 | 
            -
                def visit_multi_select_list(self, node, value):
         | 
| 358 | 
            +
                def visit_multi_select_list(self, node: MultiSelectList, value: ta.Any) -> ta.Any:
         | 
| 308 359 | 
             
                    collected = []
         | 
| 309 | 
            -
                    for child in node | 
| 360 | 
            +
                    for child in node.nodes:
         | 
| 310 361 | 
             
                        collected.append(self.visit(child, value))
         | 
| 311 362 |  | 
| 312 363 | 
             
                    return collected
         | 
| 313 364 |  | 
| 314 | 
            -
                def visit_or_expression(self, node, value):
         | 
| 315 | 
            -
                    matched = self.visit(node | 
| 365 | 
            +
                def visit_or_expression(self, node: OrExpression, value: ta.Any) -> ta.Any:
         | 
| 366 | 
            +
                    matched = self.visit(node.left, value)
         | 
| 316 367 |  | 
| 317 368 | 
             
                    if self._is_false(matched):
         | 
| 318 | 
            -
                        matched = self.visit(node | 
| 369 | 
            +
                        matched = self.visit(node.right, value)
         | 
| 319 370 |  | 
| 320 371 | 
             
                    return matched
         | 
| 321 372 |  | 
| 322 | 
            -
                def visit_and_expression(self, node, value):
         | 
| 323 | 
            -
                    matched = self.visit(node | 
| 373 | 
            +
                def visit_and_expression(self, node: AndExpression, value: ta.Any) -> ta.Any:
         | 
| 374 | 
            +
                    matched = self.visit(node.left, value)
         | 
| 324 375 |  | 
| 325 376 | 
             
                    if self._is_false(matched):
         | 
| 326 377 | 
             
                        return matched
         | 
| 327 378 |  | 
| 328 | 
            -
                    return self.visit(node | 
| 379 | 
            +
                    return self.visit(node.right, value)
         | 
| 329 380 |  | 
| 330 | 
            -
                def visit_not_expression(self, node, value):
         | 
| 331 | 
            -
                    original_result = self.visit(node | 
| 381 | 
            +
                def visit_not_expression(self, node: NotExpression, value: ta.Any) -> ta.Any:
         | 
| 382 | 
            +
                    original_result = self.visit(node.expr, value)
         | 
| 332 383 |  | 
| 333 384 | 
             
                    if _is_actual_number(original_result) and original_result == 0:
         | 
| 334 385 | 
             
                        # Special case for 0, !0 should be false, not true. 0 is not a special cased integer in jmespath.
         | 
| @@ -336,59 +387,59 @@ class TreeInterpreter(Visitor): | |
| 336 387 |  | 
| 337 388 | 
             
                    return not original_result
         | 
| 338 389 |  | 
| 339 | 
            -
                def visit_pipe(self, node, value):
         | 
| 390 | 
            +
                def visit_pipe(self, node: Pipe, value: ta.Any) -> ta.Any:
         | 
| 340 391 | 
             
                    result = value
         | 
| 341 | 
            -
                    for child in node | 
| 392 | 
            +
                    for child in [node.left, node.right]:
         | 
| 342 393 | 
             
                        result = self.visit(child, result)
         | 
| 343 394 | 
             
                    return result
         | 
| 344 395 |  | 
| 345 | 
            -
                def visit_projection(self, node, value):
         | 
| 346 | 
            -
                    base = self.visit(node | 
| 396 | 
            +
                def visit_projection(self, node: Projection, value: ta.Any) -> ta.Any:
         | 
| 397 | 
            +
                    base = self.visit(node.left, value)
         | 
| 347 398 |  | 
| 348 399 | 
             
                    allow_string = False
         | 
| 349 | 
            -
                    first_child = node | 
| 350 | 
            -
                    if first_child | 
| 351 | 
            -
                        nested_children = first_child | 
| 352 | 
            -
                        if len(nested_children) > 1 and nested_children[1] | 
| 400 | 
            +
                    first_child = node.left
         | 
| 401 | 
            +
                    if isinstance(first_child, IndexExpression):
         | 
| 402 | 
            +
                        nested_children = first_child.nodes
         | 
| 403 | 
            +
                        if len(nested_children) > 1 and isinstance(nested_children[1], Slice):
         | 
| 353 404 | 
             
                            allow_string = True
         | 
| 354 405 |  | 
| 355 406 | 
             
                    if isinstance(base, str) and allow_string:
         | 
| 356 407 | 
             
                        # projections are really sub-expressions in disguise evaluate the rhs when lhs is a sliced string
         | 
| 357 | 
            -
                        return self.visit(node | 
| 408 | 
            +
                        return self.visit(node.right, base)
         | 
| 358 409 |  | 
| 359 410 | 
             
                    if not isinstance(base, list):
         | 
| 360 411 | 
             
                        return None
         | 
| 412 | 
            +
             | 
| 361 413 | 
             
                    collected = []
         | 
| 362 414 | 
             
                    for element in base:
         | 
| 363 | 
            -
                        current = self.visit(node | 
| 415 | 
            +
                        current = self.visit(node.right, element)
         | 
| 364 416 | 
             
                        if current is not None:
         | 
| 365 417 | 
             
                            collected.append(current)
         | 
| 366 418 |  | 
| 367 419 | 
             
                    return collected
         | 
| 368 420 |  | 
| 369 | 
            -
                def visit_let_expression(self, node, value):
         | 
| 370 | 
            -
                    *bindings, expr = node['children']
         | 
| 421 | 
            +
                def visit_let_expression(self, node: LetExpression, value: ta.Any) -> ta.Any:
         | 
| 371 422 | 
             
                    scope = {}
         | 
| 372 | 
            -
                    for assign in bindings:
         | 
| 423 | 
            +
                    for assign in node.bindings:
         | 
| 373 424 | 
             
                        scope.update(self.visit(assign, value))
         | 
| 374 425 | 
             
                    self._scope.push_scope(scope)
         | 
| 375 | 
            -
                    result = self.visit(expr, value)
         | 
| 426 | 
            +
                    result = self.visit(node.expr, value)
         | 
| 376 427 | 
             
                    self._scope.pop_scope()
         | 
| 377 428 | 
             
                    return result
         | 
| 378 429 |  | 
| 379 | 
            -
                def visit_assign(self, node, value):
         | 
| 380 | 
            -
                    name = node | 
| 381 | 
            -
                    value = self.visit(node | 
| 430 | 
            +
                def visit_assign(self, node: Assign, value: ta.Any) -> ta.Any:
         | 
| 431 | 
            +
                    name = node.name
         | 
| 432 | 
            +
                    value = self.visit(node.expr, value)
         | 
| 382 433 | 
             
                    return {name: value}
         | 
| 383 434 |  | 
| 384 | 
            -
                def visit_variable_ref(self, node, value):
         | 
| 435 | 
            +
                def visit_variable_ref(self, node: VariableRef, value: ta.Any) -> ta.Any:
         | 
| 385 436 | 
             
                    try:
         | 
| 386 | 
            -
                        return self._scope[node | 
| 437 | 
            +
                        return self._scope[node.name]
         | 
| 387 438 | 
             
                    except KeyError:
         | 
| 388 | 
            -
                        raise  | 
| 439 | 
            +
                        raise UndefinedVariableError(node.name)  # noqa
         | 
| 389 440 |  | 
| 390 | 
            -
                def visit_value_projection(self, node, value):
         | 
| 391 | 
            -
                    base = self.visit(node | 
| 441 | 
            +
                def visit_value_projection(self, node: ValueProjection, value: ta.Any) -> ta.Any:
         | 
| 442 | 
            +
                    base = self.visit(node.left, value)
         | 
| 392 443 | 
             
                    try:
         | 
| 393 444 | 
             
                        base = base.values()
         | 
| 394 445 | 
             
                    except AttributeError:
         | 
| @@ -396,39 +447,62 @@ class TreeInterpreter(Visitor): | |
| 396 447 |  | 
| 397 448 | 
             
                    collected = []
         | 
| 398 449 | 
             
                    for element in base:
         | 
| 399 | 
            -
                        current = self.visit(node | 
| 450 | 
            +
                        current = self.visit(node.right, element)
         | 
| 400 451 | 
             
                        if current is not None:
         | 
| 401 452 | 
             
                            collected.append(current)
         | 
| 402 453 |  | 
| 403 454 | 
             
                    return collected
         | 
| 404 455 |  | 
| 405 | 
            -
                def _is_false(self, value):
         | 
| 456 | 
            +
                def _is_false(self, value: ta.Any) -> bool:
         | 
| 406 457 | 
             
                    # This looks weird, but we're explicitly using equality checks because the truth/false values are different
         | 
| 407 458 | 
             
                    # between python and jmespath.
         | 
| 408 | 
            -
                    return ( | 
| 459 | 
            +
                    return (
         | 
| 460 | 
            +
                        value == '' or  # noqa
         | 
| 461 | 
            +
                        value == [] or
         | 
| 462 | 
            +
                        value == {} or
         | 
| 463 | 
            +
                        value is None or
         | 
| 464 | 
            +
                        value is False
         | 
| 465 | 
            +
                    )
         | 
| 409 466 |  | 
| 410 | 
            -
                def _is_true(self, value):
         | 
| 467 | 
            +
                def _is_true(self, value: ta.Any) -> bool:
         | 
| 411 468 | 
             
                    return not self._is_false(value)
         | 
| 412 469 |  | 
| 413 470 |  | 
| 414 | 
            -
            class GraphvizVisitor | 
| 415 | 
            -
                def __init__(self):
         | 
| 471 | 
            +
            class GraphvizVisitor:
         | 
| 472 | 
            +
                def __init__(self) -> None:
         | 
| 416 473 | 
             
                    super().__init__()
         | 
| 417 | 
            -
                    self._lines = []
         | 
| 474 | 
            +
                    self._lines: list[str] = []
         | 
| 418 475 | 
             
                    self._count = 1
         | 
| 419 476 |  | 
| 420 | 
            -
                def visit(self, node | 
| 477 | 
            +
                def visit(self, node: Node) -> str:
         | 
| 421 478 | 
             
                    self._lines.append('digraph AST {')
         | 
| 422 | 
            -
                    current = f | 
| 479 | 
            +
                    current = f'{node_type(node)}{self._count}'
         | 
| 423 480 | 
             
                    self._count += 1
         | 
| 424 481 | 
             
                    self._visit(node, current)
         | 
| 425 482 | 
             
                    self._lines.append('}')
         | 
| 426 483 | 
             
                    return '\n'.join(self._lines)
         | 
| 427 484 |  | 
| 428 | 
            -
                def  | 
| 429 | 
            -
                     | 
| 430 | 
            -
             | 
| 431 | 
            -
             | 
| 485 | 
            +
                def _node_value(self, node: Node) -> lang.Maybe[ta.Any]:
         | 
| 486 | 
            +
                    if isinstance(node, (ArithmeticUnary, Arithmetic)):
         | 
| 487 | 
            +
                        return lang.just(node.operator)
         | 
| 488 | 
            +
                    elif isinstance(node, (Assign, Comparator, FunctionExpression, Field, VariableRef)):
         | 
| 489 | 
            +
                        return lang.just(node.name)
         | 
| 490 | 
            +
                    elif isinstance(node, Index):
         | 
| 491 | 
            +
                        return lang.just(node.index)
         | 
| 492 | 
            +
                    elif isinstance(node, KeyValPair):
         | 
| 493 | 
            +
                        return lang.just(node.key_name)
         | 
| 494 | 
            +
                    elif isinstance(node, Literal):
         | 
| 495 | 
            +
                        return lang.just(node.literal_value)
         | 
| 496 | 
            +
                    else:
         | 
| 497 | 
            +
                        return lang.empty()
         | 
| 498 | 
            +
             | 
| 499 | 
            +
                def _visit(self, node: Node, current: str) -> None:
         | 
| 500 | 
            +
                    self._lines.append(
         | 
| 501 | 
            +
                        f'{current} '
         | 
| 502 | 
            +
                        f'[label="{node_type(node)}({self._node_value(node).map(json.dumps).map(html.escape).or_else("")})"]',
         | 
| 503 | 
            +
                    )
         | 
| 504 | 
            +
                    for child in node.children:
         | 
| 505 | 
            +
                        child_name = f'{node_type(child)}{self._count}'
         | 
| 432 506 | 
             
                        self._count += 1
         | 
| 433 507 | 
             
                        self._lines.append(f'  {current} -> {child_name}')
         | 
| 434 508 | 
             
                        self._visit(child, child_name)
         | 
| @@ -16,9 +16,9 @@ class PydevdPlugin: | |
| 16 16 | 
             
                        # if (dbg := opd.get_global_debugger()) is not None:
         | 
| 17 17 | 
             
                        #     dbg.set_unit_tests_debugging_mode()
         | 
| 18 18 |  | 
| 19 | 
            -
                def pytest_exception_interact(self, node, call, report):
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 19 | 
            +
                # def pytest_exception_interact(self, node, call, report):
         | 
| 20 | 
            +
                #     if opd.get_setup() is not None:
         | 
| 21 | 
            +
                #         if not node.session.config.option.no_pydevd:
         | 
| 22 | 
            +
                #             opd.debug_unhandled_exception(call.excinfo._excinfo)  # noqa
         | 
| 23 | 
            +
                #
         | 
| 24 | 
            +
                #     return report
         |