Source code for ways.parsing.trace

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''A module to help you debug all of your Context, Plugin, and Action objects.'''


# IMPORT STANDARD LIBRARIES
# scspell-id: 3c62e4aa-c280-11e7-be2b-382c4ac59cfd
import uuid
import functools
import collections

# IMPORT THIRD-PARTY LIBRARIES
import six

# IMPORT WAYS LIBRARIES
import ways

# IMPORT LOCAL LIBRARIES
from ..base import situation as sit
from ..core import loop
from ..helper import common


def _create_fake_uuid():
    return 'ways_generated-' + uuid.uuid4().hex


def _return_tail_of_hierarchy(obj):
    return obj[-1]


def _get_uuid_from_dict(obj):
    try:
        uuid_ = obj[common.WAYS_UUID_KEY]
    except (AttributeError, KeyError):
        uuid_ = _create_fake_uuid()

    return uuid_


def _get_ways_uuid_from_descriptor(obj):
    info = common.decode(obj.get('item'))
    if info is None:
        info = dict()

    return _get_uuid_from_dict(info)


def _get_ways_uuid_from_plugin(obj):
    try:
        uuid_ = obj[common.WAYS_UUID_KEY]
    except KeyError:
        try:
            uuid_ = obj['item']
        except KeyError:
            uuid_ = _create_fake_uuid()

    return uuid_


[docs]def startswith(base, leaf): '''Check if all tuple items match the start of another tuple. Raises: ValueError: If base is shorted than leaf. ''' if len(base) < len(leaf): raise ValueError('Base cannot be smaller than leaf') for root, item in six.moves.zip(base, leaf): if root != item: return False return True
[docs]def trace_actions(obj, *args, **kwargs): # noqa: D301 '''Get actions that are assigned to the given object. Args: obj (:class:`ways.api.Action` or \ :class:`ways.api.AssetFinder` or \ :class:`ways.api.Context` or \ :class:`ways.api.Find`): The object to get the actions of. *args (list): Position args to pass to ways.get_actions. **kwargs (dict[str]): Keyword args to pass to ways.get_actions. Returns: list[:class:`ways.api.Action` or callable]: The actions in the hierarchy. ''' hierarchy = trace_hierarchy(obj) return ways.get_actions(hierarchy, *args, **kwargs)
[docs]def trace_action_names(obj, *args, **kwargs): # noqa: D301 '''Get the names of all actions available to a Ways object. Args: obj (:class:`ways.api.Action` or \ :class:`ways.api.AssetFinder` or \ :class:`ways.api.Context` or \ :class:`ways.api.Find`): The object to get the action names of. *args (list): Position args to pass to ways.get_action_names. **kwargs (dict[str]): Keyword args to pass to ways.get_action_names. Returns: list[str]: The names of all actions found for the Ways object. ''' hierarchy = trace_hierarchy(obj) return ways.get_action_names(hierarchy, *args, **kwargs)
[docs]def trace_actions_table(obj, *args, **kwargs): # noqa: D301 '''Find the names and objects of every action registered to Ways. Args: obj (:class:`ways.api.Action` or \ :class:`ways.api.AssetFinder` or \ :class:`ways.api.Context` or \ :class:`ways.api.Find`): The object to get the available actions table of. *args (list): Position args to pass to ways.get_actions_info.. **kwargs (dict[str]): Keyword args to pass to ways.get_action_info. Returns: dict[str, :class:`ways.api.Action` or callable]: The names and actions of an object. ''' hierarchy = trace_hierarchy(obj) return ways.get_actions_info(hierarchy, *args, **kwargs)
[docs]def trace_all_descriptor_results(): '''list[dict[str]]: The load/failure information about each Descriptor.''' return ways.DESCRIPTOR_LOAD_RESULTS
[docs]def trace_all_plugin_results(): '''list[dict[str]]: The results of each plugin's load results.''' return ways.PLUGIN_LOAD_RESULTS
[docs]def trace_all_load_results(): '''Get the load results of every plugin and descriptor. If the UUID for a Descriptor cannot be found, Ways will automatically assign it a UUID. Using this function we can check 1. What plugins that Ways found and tried to load. 2. If our plugin loaded and, if not, why. Returns: dict[str, :class:`collections.OrderedDict` [str, dict[str]]]: The main dictionary has two keys, "descriptors" and "plugins". Each key has an OrderedDict that contains the UUID of each Descriptor and plugin and their objects. ''' info = dict() info['descriptors'] = collections.OrderedDict() for result in trace_all_descriptor_results(): info['descriptors'][_get_ways_uuid_from_descriptor(result)] = result info['plugins'] = collections.OrderedDict() for result in trace_all_plugin_results(): info['plugins'][_get_ways_uuid_from_plugin(result)] = result return info
[docs]def trace_context(obj): '''Get a Context, using some object. This function assumes that the given object is a Ways class that only has 1 Context added to it (not several). Args: obj: Some Ways object instance. Returns: :class:`ways.api.Context` or NoneType: The found Context. ''' if isinstance(obj, sit.Context): # Is it a Context already? If so, return it return obj # Is it a AssetFinder? if 'finder' in dir(obj): obj = obj.finder # Try to find the context - assuming obj was finder.Find or an action, etc. try: context_ = obj.context if isinstance(context_, sit.Context): return context_ except AttributeError: pass try: # Maybe this is a hierarchy. In which case, use it to create a Context return sit.get_context(obj) except Exception: pass return None
[docs]def trace_assignment(obj): '''str: Get the assignment for this object.''' try: obj = obj.finder except AttributeError: pass try: obj = obj.context except AttributeError: pass try: return obj.get_assignment() except AttributeError: return common.DEFAULT_ASSIGNMENT
[docs]def trace_hierarchy(obj): # noqa: D301 '''Try to find a hierarchy for the given object. Args: obj (:class:`ways.api.Action` or \ :class:`ways.api.AssetFinder` or \ :class:`ways.api.Context` or \ :class:`ways.api.Find`): The object to get the hierarchy of. Returns: tuple[str]: The hierarchy of some object. ''' hierarchy = '' try: hierarchy = obj.get_hierarchy() except AttributeError: pass if hierarchy: return common.split_hierarchy(hierarchy) obj = trace_context(obj) if obj is None: return tuple() hierarchy = tuple() try: hierarchy = obj.hierarchy except AttributeError: pass if hierarchy: return common.split_hierarchy(hierarchy) return hierarchy
def __default_hook(obj): '''Return back the original object.''' return obj def __default_predicate(obj): '''Return True.''' return bool(obj) def _get_hierarchy_tree( hierachies, predicate=__default_predicate, hook=__default_hook): '''Iterate over a tree of hierarchies, producing a dict-tree. Args: hierachies (list[tuple[str]]): The hierachies to build into a tree. predicate (:obj:`callable[str or tuple[str]]`, optional): If False, the hierarchy will not be added to the tree. If True, the hierarchy will be added to the tree. hook (:obj:`callable[str or tuple[str]]`, optional): A function to run on a part before it is added to the tree. Returns: dict: The final hierarchy tree. ''' output = dict() for hierarchy in hierachies: previous_dict = output for part in loop.walk_items(hierarchy): if not predicate(part): continue part = hook(part) previous_dict.setdefault(part, dict()) previous_dict = previous_dict[part] return output
[docs]def get_action_hierarchies(action): '''Get the Context hierachies that this Action is registered for. .. note :: get_action_hierarchies will return every Action that matches the given Action name. So if multiple classes/functions are all registered under the same name, then every hierarchy that those Actions use will be returned. However, if a object like a function or class that was registered, only that object's hierarchies will be returned. Args: action (str or class or callable): The action to get the hierachies of. Returns: set[tuple[str]]: The hierarchies for the given Action. ''' actions = get_all_action_hierarchies() if action in actions: return actions[action]['hierarchies'] output = set() for info in six.itervalues(actions): if action == info['name']: output.update(info['hierarchies']) return output
[docs]def get_all_action_hierarchies(): '''Organize every Action that is registered into Ways by object and hierarchy. Returns: dict[class or callable: dict[str: str or set]]: Actions are stored as either classes or functions. Each Action's value is a dict which contains the hierachies that the Action is applied to and its registered name. ''' actions = dict() for hierarchy, info in six.iteritems(ways.ACTION_CACHE): for action_info in six.itervalues(info): for name, action in six.iteritems(action_info): actions.setdefault(action, dict()) actions[action].setdefault('hierarchies', set()) actions[action]['hierarchies'].add(hierarchy) actions[action]['name'] = name return actions
[docs]def get_all_hierarchies(): '''set[tuple[str]]: The Contexts that have plugins in our environment.''' return set(trace_hierarchy(plug) for plug in ways.PLUGIN_CACHE.get('all', []))
[docs]def get_all_hierarchy_trees(full=False): '''Get a description of every Ways hierarchy. Examples: >>> get_all_hierarchy_trees(full=True) >>> { >>> ('foo', ): { >>> ('foo', 'bar'): { >>> ('foo' 'bar', 'fizz'): {}, >>> }, >>> ('foo', 'something', 'buzz'): { >>> ('foo', 'something', 'buzz', 'thing'): {}, >>> }, >>> }, >>> } >>> get_all_hierarchy_trees(full=False) >>> { >>> 'foo': { >>> 'bar': { >>> 'fizz': {}, >>> }, >>> 'something': { >>> 'buzz': { >>> 'thing': {}, >>> }, >>> }, >>> }, >>> } Args: full (:obj:`bool`, optional): If True, each item in the dict will be its own hierarchy. If False, only a single part will be written. See examples for details. Default is False. Returns: :class:`collections.defaultdict[str]`: The entire hierarchy. ''' if not full: return _get_hierarchy_tree(get_all_hierarchies(), hook=_return_tail_of_hierarchy) return _get_hierarchy_tree(get_all_hierarchies())
[docs]def get_all_assignments(): '''set[str]: All of the assignments found in our environment.''' return set(trace_assignment(plug) for plug in ways.PLUGIN_CACHE.get('all', []))
[docs]def get_child_hierarchies(hierarchy): '''list[tuple[str]]: Get hierarchies that depend on the given hierarchy.''' base_hierarchy = trace_hierarchy(hierarchy) children = set() for plugin in ways.PLUGIN_CACHE['all']: hierarchy = plugin.get_hierarchy() # If the plugin's hierarchy is less than the base, it is probably # above it. Which means it doesn't inherit from this hierarchy # is_parent = len(hierarchy) < len(base_hierarchy) if not is_parent and base_hierarchy != hierarchy and startswith(hierarchy, base_hierarchy): children.add(hierarchy) return children
[docs]def get_child_hierarchy_tree(hierarchy, full=False): '''Get all of the hierarchies that inherit the given hierarchy. Examples: >>> get_all_hierarchy_trees(full=True) >>> { >>> ('foo', ): { >>> ('foo', 'bar'): { >>> ('foo' 'bar', 'fizz'): {}, >>> }, >>> ('foo', 'something', 'buzz'): { >>> ('foo', 'something', 'buzz', 'thing'): {}, >>> }, >>> }, >>> } >>> get_all_hierarchy_trees(full=False) >>> { >>> 'foo': { >>> 'bar': { >>> 'fizz': {}, >>> }, >>> 'something': { >>> 'buzz': { >>> 'thing': {}, >>> }, >>> }, >>> }, >>> } Args: hierarchy (tuple[str]): The hierarchy to get the child hierarchy items of. full (:obj:`bool`, optional): If True, each item in the dict will be its own hierarchy. If False, only a single part will be written. See examples for details. Default is False. Returns: :class:`collections.defaultdict[str]`: The entire hierarchy. ''' def try_startswith(hierarchy, obj): '''Check if a hierarchy starts with another hierarchy. If the startswith function raises a ValueError, just assume False. ''' if hierarchy == obj: return False try: return startswith(obj, hierarchy) except ValueError: return False hierarchy = trace_hierarchy(hierarchy) children = get_child_hierarchies(hierarchy) if not full: return _get_hierarchy_tree(children, predicate=functools.partial(try_startswith, hierarchy), hook=_return_tail_of_hierarchy) return _get_hierarchy_tree( children, predicate=functools.partial(try_startswith, hierarchy))