About Anaconda Help Download Anaconda

Meepio / packages / pure-eval 0.2.1

  • 30 total downloads
  • Last upload: 3 years and 2 days ago

Installers

pip install

To install this package run one of the following:
pip install -i https://pypi.anaconda.org/meepio/simple pure-eval

Description

pure_eval

Build Status Coverage Status Supports Python versions 3.5+

This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects.

It can be installed from PyPI:

pip install pure_eval

To demonstrate usage, suppose we have an object defined as follows:

class Rectangle:
            def init(self, width, height):
                self.width = width
                self.height = height

@property def area(self): print("Calculating area...") return self.width * self.height

rect = Rectangle(3, 5)

Given the rect object, we want to evaluate whatever expressions we can in this source code:

source = "(rect.width, rect.height, rect.area)"
        

This library works with the AST, so let's parse the source code and peek inside:

import ast

tree = ast.parse(source) thetuple = tree.body[0].value for node in thetuple.elts: print(ast.dump(node))

Output:

Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load())
        Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load())
        Attribute(value=Name(id='rect', ctx=Load()), attr='area', ctx=Load())
        

Now to actually use the library. First construct an Evaluator:

from pure_eval import Evaluator

evaluator = Evaluator({"rect": rect})

The argument to Evaluator should be a mapping from variable names to their values. Or if you have access to the stack frame where rect is defined, you can instead use:

evaluator = Evaluator.from_frame(frame)
        

Now to evaluate some nodes, using evaluator[node]:

print("rect.width:", evaluator[thetuple.elts[0]])
        print("rect:", evaluator[thetuple.elts[0].value])
        

Output:

rect.width: 3
rect: <__main__.Rectangle object at 0x105b0dd30>

OK, but you could have done the same thing with eval. The useful part is that it will refuse to evaluate the property rect.area because that would trigger unknown code. If we try, it'll raise a CannotEval exception.

from pure_eval import CannotEval

try: print("rect.area:", evaluator[the_tuple.elts[2]]) # fails except CannotEval as e: print(e) # prints CannotEval

To find all the expressions that can be evaluated in a tree:

for node, value in evaluator.find_expressions(tree):
            print(ast.dump(node), value)
        

Output:

Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) 3
        Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) 5
        Name(id='rect', ctx=Load()) <main.Rectangle object at 0x105568d30>
        Name(id='rect', ctx=Load()) <main.Rectangle object at 0x105568d30>
        Name(id='rect', ctx=Load()) <main.Rectangle object at 0x105568d30>
        

Note that this includes rect three times, once for each appearance in the source code. Since all these nodes are equivalent, we can group them together:

from pureeval import groupexpressions

for nodes, values in groupexpressions(evaluator.findexpressions(tree)): print(len(nodes), "nodes with value:", values)

Output:

1 nodes with value: 3
1 nodes with value: 5
3 nodes with value: <__main__.Rectangle object at 0x10d374d30>
    If we want to list all the expressions in a tree, we may want to filter out certain expressions whose values are obvious. For example, suppose we have a function `foo`:
    <div class="codehilite">
    <pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
        <span class="k">pass</span>
    </code></pre>
    </div>


    If we refer to `foo` by its name as usual, then that's not interesting:
    <div class="codehilite">
    <pre><span></span><code><span class="kn">from</span> <span class="nn">pure_eval</span> <span class="kn">import</span> <span class="n">is_expression_interesting</span>

    <span class="n">node</span> <span class="o">=</span> <span class="n">ast</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;foo&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">body</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">value</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">ast</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">node</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">is_expression_interesting</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">foo</span><span class="p">))</span>
    </code></pre>
    </div>


    Output:
    <div class="codehilite">
    <pre><span></span><code><span class="n">Name</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s1">&#39;foo&#39;</span><span class="p">,</span> <span class="n">ctx</span><span class="o">=</span><span class="n">Load</span><span class="p">())</span>
    <span class="kc">False</span>
    </code></pre>
    </div>


    But if we refer to it by a different name, then it's interesting:
    <div class="codehilite">
    <pre><span></span><code><span class="n">node</span> <span class="o">=</span> <span class="n">ast</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;bar&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">body</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">value</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">ast</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">node</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">is_expression_interesting</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">foo</span><span class="p">))</span>
    </code></pre>
    </div>


    Output:
    <div class="codehilite">
    <pre><span></span><code><span class="n">Name</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s1">&#39;bar&#39;</span><span class="p">,</span> <span class="n">ctx</span><span class="o">=</span><span class="n">Load</span><span class="p">())</span>
    <span class="kc">True</span>
    </code></pre>
    </div>


    In general `is_expression_interesting` returns False for the following values:
    - Literals (e.g. `123`, `'abc'`, `[1, 2, 3]`, `{'a': (), 'b': ([1, 2], [3])}`)
    - Variables or attributes whose name is equal to the value's `__name__`, such as `foo` above or `self.foo` if it was a method.
    - Builtins (e.g. `len`) referred to by their usual name.

    To make things easier, you can combine finding expressions, grouping them, and filtering out the obvious ones with:
    <div class="codehilite">
    <pre><span></span><code><span class="n">evaluator</span><span class="o">.</span><span class="n">interesting_expressions_grouped</span><span class="p">(</span><span class="n">root</span><span class="p">)</span>
    </code></pre>
    </div>


    To get the source code of an AST node, I recommend [asttokens](https://github.com/gristlabs/asttokens).

    Here's a complete example that brings it all together:
    <div class="codehilite">
    <pre><span></span><code><span class="kn">from</span> <span class="nn">asttokens</span> <span class="kn">import</span> <span class="n">ASTTokens</span>
    <span class="kn">from</span> <span class="nn">pure_eval</span> <span class="kn">import</span> <span class="n">Evaluator</span>

    <span class="n">source</span> <span class="o">=</span> <span class="s2">&quot;&quot;&quot;</span>
    <span class="s2">x = 1</span>
    <span class="s2">d = </span><span class="si">{x: 2}</span>
    <span class="s2">y = d[x]</span>
    <span class="s2">&quot;&quot;&quot;</span>

    <span class="n">names</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="n">exec</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">names</span><span class="p">)</span>
    <span class="n">atok</span> <span class="o">=</span> <span class="n">ASTTokens</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">parse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">nodes</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">Evaluator</span><span class="p">(</span><span class="n">names</span><span class="p">)</span><span class="o">.</span><span class="n">interesting_expressions_grouped</span><span class="p">(</span><span class="n">atok</span><span class="o">.</span><span class="n">tree</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">atok</span><span class="o">.</span><span class="n">get_text</span><span class="p">(</span><span class="n">nodes</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="s2">&quot;=&quot;</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
    </code></pre>
    </div>


    Output:
    <div class="codehilite">
    <pre><span></span><code><span class="n">x</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="n">d</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
    <span class="n">y</span> <span class="o">=</span> <span class="mi">2</span>
    <span class="n">d</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span>
    </code></pre>
    </div>

© 2024 Anaconda, Inc. All Rights Reserved. (v4.0.6) Legal | Privacy Policy