Demo entry 6770065



Submitted by anonymous on Nov 11, 2018 at 15:32
Language: Cython. Code size: 5.4 kB.

"""Classes that group all functions that operate on source code"""
# pylint: disable=exec-used, no-name-in-module

import sys
import dis
import inspect
from types import CodeType
from collections import namedtuple
from contextlib import contextmanager
from splinter import DecoratedObject
from utilities import safedict

if sys.version_info[:3] >= (3, 7, 0):
    from io import StringIO
    from cStringIO import StringIO  # Removed from python 3.7.0

__all__ = ['SourceCodeParser', ]

class SourceCodeParser(object):
    """Object that operates mostly on the source code of the decorated function.
    Extracting, cleaning and recreating source code etc."""

    Return = namedtuple('Return', 'func_return debug_return'.split())
    Return.__new__.__defaults__ = (None, None)

    # Slice indicators
    debugly_slice = -1  # Always last element in return
    function_slice = slice(0, -1)  # Everything except last element in return
    # Find and grab everything in between ( --> [ in a string
    find_args = slice(CodeType.__doc__.find('(') + 1, CodeType.__doc__.find('['))

    # Methods
    is_builtin = lambda self, var: var in self.object_builtins.keys() \
        if isinstance(self.object_builtins, dict) \
        else var in self.object_builtins.__dict__.keys()
    # Find and grab everything in between ( --> ) in a string
    find_vars = staticmethod(lambda line: slice(line.find('(') + 1, line.find(')')))

    def __init__(self, object_):
        self.object_ = object_
        self.object_builtins = self.object_.__globals__['__builtins__']
        self.source_code = inspect.getsource(object_)
        self.helper_functions = DecoratedObject

    def clean_source_code(self):
        """Remove decorator and indentation from function definition"""

        source_code = (line for line in self.source_code.splitlines())
        next(source_code)  # Skip decorator
        main_code = '\n'.join(source_code)
        self.source_code = self.remove_indentation(main_code)

    def generate_code_object(func, parsed_code):
        """Copy original code object from original function
        to newly defined function"""

        environ = {'CodeType': CodeType, 'func': func}
        # Dictionary that handles KeyError or missing key
        safe_dict = safedict({'codestring': 'code', 'constants': 'consts'})
        code_type_params = [safe_dict[arg.strip()]
                            for arg in CodeType.__doc__[SourceCodeParser.find_args].split(',')]
        orig_args = {'filename', 'firstlineno', 'lnotab'}  # Arguments to keep from original function

        # Compile and create new function from ast nodes
        compiled_func = compile(parsed_code, '<string>', 'exec')
        exec compiled_func in environ

        # Create new code type object that will be attached to function
        code_type_args = ['{}.__code__.co_{}'.format(func.__name__, arg) if arg not in orig_args
                          else 'func.__code__.co_{}'.format(arg) for arg in code_type_params]
        create_code = 'code = CodeType({})'.format(', '.join(code_type_args))

        # Compile and create new CodeType object
        compiled_code = compile(create_code, '<string>', 'exec')
        exec compiled_code in environ

        return environ['code']

    def parse_return(function_return):
        """Split debug return from normal function return

            function_return -- Normal function return

        parsed_return = SourceCodeParser.Return()

        if isinstance(function_return, dict):
            return parsed_return._replace(**{'debug_return': function_return})

        func_return = function_return[SourceCodeParser.function_slice]

        return_fields = {'func_return': func_return[0] if len(func_return) == 1 else func_return,
                         'debug_return': function_return[SourceCodeParser.debugly_slice]}

        return parsed_return._replace(**return_fields)

    def get_globals(self):
        """Get function's global variables and differentiate
        between loading global and storing global variable"""

        output = StringIO()
        vars_ = {'LOAD_GLOBAL', 'STORE_GLOBAL'}

        with self.capture_dis(output):

        store_and_load = [{var: set(line[self.find_vars(line)]
                                    for line in output.getvalue().splitlines()
                                    if var in line)}
                          for var in vars_]

        merged = dict(store_and_load.pop())

        return {var: {glob: self.object_.__globals__[glob] for glob in globals_
                      if not self.is_builtin(glob)} for var, globals_ in merged.items()}

    def __getattr__(self, item):
        """Delegate attribute access to DecoratedObject class"""

        return getattr(self.helper_functions, item)

    def capture_dis(output):
        """Context manager that will redirect stdout into specific output"""

        stdout = sys.stdout
        sys.stdout = output

            sys.stdout = stdout

This snippet took 0.01 seconds to highlight.

Back to the Entry List or Home.

Delete this entry (admin only).