<commit_before>"""
Copyright (C) 2013-2015 Stefan Pfenninger.
Licensed under the Apache 2.0 License (see LICENSE file).

utils.py
~~~~~~~~

Various utility functions, particularly the AttrDict class (a subclass
of regular dict) used for managing model configuration.

"""

from contextlib import contextmanager
from io import StringIO
import functools
import os

import numpy as np
import yaml

from . import exceptions

class AttrDict(dict):
    """
    A subclass of ``dict`` with key access by attributes::

        d = AttrDict({'a': 1, 'b': 2})
        d.a == 1  # True

    Includes a range of additional methods to read and write to YAML,
    and to deal with nested keys.

    """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __init__(self, source_dict=None):
        super(AttrDict, self).__init__()
        if isinstance(source_dict, dict):
            self.init_from_dict(source_dict)

    def copy(self):
        """Override copy method so that it returns an AttrDict"""
        return AttrDict(dict(self).copy())

    def init_from_dict(self, d):
        """
        Initialize a new AttrDict from the given dict. Handles any
        nested dicts by turning them into AttrDicts too::

            d = AttrDict({'a': 1, 'b': {'x': 1, 'y': 2}})
            d.b.x == 1  # True

        """
        for k, v in d.items():
            # First, keys must be strings, not ints
            if isinstance(k, int):
                k = str(k)
            # Now, assign to the key, handling nested AttrDicts properly
            if isinstance(v, dict):
                self.set_key(k, AttrDict(v))
            elif isinstance(v, list):
                self.set_key(k, [i if not isinstance(i, dict) else AttrDict(i)
                                 for i in v])
            else:
                self.set_key(k, v)

    @classmethod
    def from_yaml(cls, f, resolve_imports=True):
        """
        Returns an AttrDict initialized from the given path or
        file object ``f``, which must point to a YAML file.

        If ``resolve_imports`` is True, ``import:`` statements are
        resolved recursively, else they are treated like any other key.

        When resolving import statements, anything defined locally
        overrides definitions in the imported file.

        """
        if isinstance(f, str):
            with open(f, 'r') as src:
                loaded = cls(yaml.load(src))
        else:
            loaded = cls(yaml.load(f))
        if resolve_imports and 'import' in loaded:
            for k in loaded['import']:
                imported = cls.from_yaml(relative_path(k, f))
                # loaded is added to imported (i.e. it takes precedence)
                imported.union(loaded)
                loaded = imported
            # 'import' key no longer needed, so we drop it
            loaded.pop('import', None)
        return loaded

    @classmethod
    def from_yaml_string(cls, string):
        """
        Returns an AttrDict initialized from the given string, which
        must be valid YAML.

        """
        return cls(yaml.load(string))

    def set_key(self, key, value):
        """
        Set the given ``key`` to the given ``value``. Handles nested
        keys, e.g.::

            d = AttrDict()
            d.set_key('foo.bar', 1)
            d.foo.bar == 1  # True

        """
        if '.' in key:
            key, remainder = key.split('.', 1)
            try:
                self[key].set_key(remainder, value)
            except KeyError:
                self[key] = AttrDict()
                self[key].set_key(remainder, value)
            except AttributeError:
                if self[key] is None:  # If the value is None, we replace it
                    self[key] = AttrDict()
                    self[key].set_key(remainder, value)
                # Else there is probably something there, and we don't just
                # want to overwrite so stop and warn the user
                else:
                    raise KeyError('Cannot set nested key on non-dict key.')
        else:
            self[key] = value

    def get_key(self, key, default=None):
        """
        Looks up the given ``key``. Like set_key(), deals with nested
        keys.

        If default is given and not None (it may be, for example, False),
        returns default if encounters a KeyError during lookup

        """
        if '.' in key:
            # Nested key of form "foo.bar"
            key, remainder = key.split('.', 1)
            if default is not None:
                try:
                    value = self[key].get_key(remainder, default)
                except KeyError:
                    # subdict exists, but doesn't contain key
                    return default
                except AttributeError:
                    # key points to non-dict thing, so no get_key attribute
                    return default
            else:
                value = self[key].get_key(remainder)
        else:
            # Single, non-nested key of form "foo"
            if default is not None:
                return self.get(key, default)
            else:
                return self[key]
        return value

    def del_key(self, key):
        """Delete the given key. Properly deals with nested keys."""
        if '.' in key:
            key, remainder = key.split('.', 1)
            try:
                del self[key][remainder]
            except KeyError:
                self[key].del_key(remainder)
        else:
            del self[key]

    def as_dict(self, flat=False):
        """
        Return the AttrDict as a pure dict (with nested dicts if
        necessary).

        """
        if not flat:
            return self.as_dict_nested()
        else:
            return self.as_dict_flat()

    def as_dict_nested(self):
        d = {}
        for k, v in self.items():
            if isinstance(v, AttrDict):
                d[k] = v.as_dict()
            elif isinstance(v, list):
                d[k] = [i if not isinstance(i, AttrDict) else i.as_dict()
                        for i in v]
            else:
                d[k] = v
        return d

    def as_dict_flat(self):
        d = {}
        keys = self.keys_nested()
        for k in keys:
            d[k] = self.get_key(k)
        return d

    def to_yaml(self, path=None, convert_objects=True, **kwargs):
        """
        Saves the AttrDict to the given path as a YAML file.

        If ``path`` is None, returns the YAML string instead.

        Any additional keyword arguments are passed to the YAML writer,
        so can use e.g. ``indent=4`` to override the default of 2.

        ``convert_objects`` (defaults to True) controls whether Numpy
        objects should be converted to regular Python objects, so that
        they are properly displayed in the resulting YAML output.

        """
        if convert_objects:
            result = self.copy()
            for k in result.keys_nested():
                # Convert numpy numbers to regular python ones
                v = result.get_key(k)
                if isinstance(v, np.floating):
                    result.set_key(k, float(v))
                elif isinstance(v, np.integer):
                    result.set_key(k, int(v))
            result = result.as_dict()
        else:
            result = self.as_dict()
        if path is not None:
            with open(path, 'w') as f:
                yaml.dump(result, f, **kwargs)
        else:
            return yaml.dump(result, **kwargs)

    def keys_nested(self, subkeys_as='list'):
        """
        Returns all keys in the AttrDict, sorted, including the keys of
        nested subdicts (which may be either regular dicts or AttrDicts).

        If ``subkeys_as='list'`` (default), then a list of
        all keys is returned, in the form ``['a', 'b.b1', 'b.b2']``.

        If ``subkeys_as='dict'``, a list containing keys and dicts of
        subkeys is returned, in the form ``['a', {'b': ['b1', 'b2']}]``.

        """
        keys = []
        for k, v in sorted(self.items()):
            if isinstance(v, AttrDict) or isinstance(v, dict):
                if subkeys_as == 'list':
                    keys.extend([k + '.' + kk for kk in v.keys_nested()])
                elif subkeys_as == 'dict':
                    keys.append({k: v.keys_nested(subkeys_as=subkeys_as)})
            else:
                keys.append(k)
        return keys

    def union(self, other, allow_override=False, allow_replacement=False):
        """
        Merges the AttrDict in-place with the passed ``other``
        AttrDict. Keys in ``other`` take precedence, and nested keys
        are properly handled.

        If ``allow_override`` is False, a KeyError is raised if
        other tries to redefine an already defined key.

        If ``allow_replacement``, allow "_REPLACE_" key to replace an
        entire sub-dict.

        """
        if allow_replacement:
            WIPE_KEY = '_REPLACE_'
            override_keys = [k for k in other.keys_nested()
                             if WIPE_KEY not in k]
            wipe_keys = [k.split('.' + WIPE_KEY)[0]
                         for k in other.keys_nested()
                         if WIPE_KEY in k]
        else:
            override_keys = other.keys_nested()
            wipe_keys = []
        for k in override_keys:
            if not allow_override and k in self.keys_nested():
                raise KeyError('Key defined twice: {}'.format(k))
            else:
                self.set_key(k, other.get_key(k))
        for k in wipe_keys:
            self.set_key(k, other.get_key(k + '.' + WIPE_KEY))

@contextmanager
def capture_output():
    """
    Capture stdout and stderr output of a wrapped function::

        with capture_output() as out:
            # do things that create stdout or stderr output

    Returns a list with the captured strings: ``[stderr, stdout]``

    """
    import sys
    old_out, old_err = sys.stdout, sys.stderr
    try:
        out = [StringIO(), StringIO()]
        sys.stdout, sys.stderr = out
        yield out
    finally:
        sys.stdout, sys.stderr = old_out, old_err
        out[0] = out[0].getvalue()
        out[1] = out[1].getvalue()

# This used to be a custom function, but as of Python 3.2 we can use
# the built-in lru_cache for simplicity
memoize = functools.lru_cache(maxsize=512)

class memoize_instancemethod(object):
    """
    Cache the return value of a method on a per-instance basis
    (as opposed to a per-class basis like functools.lru_cache does)

    Source: http://code.activestate.com/recipes/577452/

    This class is meant to be used as a decorator of methods. The return
    value from a given method invocation will be cached on the instance
    whose method was invoked. All arguments passed to a method decorated
    with memoize must be hashable.

    If a memoized method is invoked directly on its class the result
    will not be cached. Instead the method will be invoked like a
    static method.

    """
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self.func
        return functools.partial(self, obj)

    def __call__(self, *args, **kw):
        obj = args[0]
        try:
            cache = obj.__cache
        except AttributeError:
            cache = obj.__cache = {}
        key = (self.func, args[1:], frozenset(list(kw.items())))
        try:
            res = cache[key]
        except KeyError:
            res = cache[key] = self.func(*args, **kw)
        return res

def relative_path(path, base_path_file):
    """
    If ``path`` is not absolute, it is interpreted as relative to the
    path of the given ``base_path_file``.

    """
    # Check if base_path_file is a string because it might be an AttrDict
    if not os.path.isabs(path) and isinstance(base_path_file, str):
        path = os.path.join(os.path.dirname(base_path_file), path)
    return path

def option_getter(config_model, data):
    """Returns a get_option() function using the given config_model and data"""
    o = config_model
    d = data

    def get_option(option, x=None, default=None, ignore_inheritance=False):

        def _get_option(opt, fail=False):
            try:
                result = o.get_key('techs.' + opt)
            except KeyError:
                if ignore_inheritance:
                    return _get_option(default, fail)
                # 'ccgt.constraints.s_time' -> 'ccgt', 'constraints.s_time'
                tech, remainder = opt.split('.', 1)
                if ':' in tech:
                    parent = tech.split(':')[0]
                else:
                    # parent = e.g. 'defaults'
                    parent = o.get_key('techs.' + tech + '.parent')
                try:
                    result = _get_option(parent + '.' + remainder, fail)
                except KeyError:
                    e = exceptions.OptionNotSetError
                    if fail:
                        raise e('Failed to read option `{}` '
                                'with given default '
                                '`{}`'.format(option, default))
                    elif default:
                        result = _get_option(default, fail=True)
                    elif tech == 'defaults':
                        raise e('Reached top of inheritance chain '
                                'and no default defined for: '
                                '`{}`'.format(option))
                    else:
                        raise e('Can not get parent for `{}` '
                                'and no default defined '
                                '({}).'.format(tech, option))
            return result

        def _get_location_option(key, location):

            def getter(key, location):
                # NB: KeyErrors raised here are always caught within _get_option
                # so need no further information or handling
                # Raises KeyError if the specific _override column does not exist
                result = d.locations.at[location, '_override.' + key]
                # Also raise KeyError if the result is NaN, i.e. if no
                # location-specific override has been defined
                try:
                    if np.isnan(result):
                        raise KeyError
                # Have to catch this because np.isnan not implemented for strings
                except TypeError:
                    pass
                return result

            while True:
                try:
                    return getter(key, location)
                except KeyError:
                    parent_location = d.locations.at[location, '_within']
                    if parent_location:  # Will be None if no parent
                        return getter(key, parent_location)
                    else:
                        # Once top of "location inheritance" chain reached,
                        # raise KeyError, which will cause the calling function
                        # to fall back to non-location specific settings
                        raise

        if x:
            try:
                result = _get_location_option(option, x)
            # If can't find a location-specific option, fall back to model-wide
            except KeyError:
                result = _get_option(option)
        else:
            result = _get_option(option)
        # Deal with 'inf' settings
        if result == 'inf':
            result = float('inf')
        return result

    return get_option

def cost_getter(option_getter, costs_type='costs'):
    def get_cost(cost, y, k):
        return option_getter(y + '.' + costs_type + '.' + k + '.' + cost,
                             default=y + '.' + costs_type + '.default.' + cost)
    return get_cost

def cost_per_distance_getter(option_getter):
    def get_cost_per_distance(cost, y, k, x):
        try:
            cost_option = y + '.costs_per_distance.' + k + '.' + cost
            cost = option_getter(cost_option)
            per_distance = option_getter(y + '.per_distance')
            distance = option_getter(y + '.distance', x=x)
            distance_cost = cost * (distance / per_distance)
        except exceptions.OptionNotSetError:
            distance_cost = 0
        return distance_cost
    return get_cost_per_distance

def depreciation_getter(option_getter):
    def get_depreciation_rate(y, k):
        interest = option_getter(y + '.depreciation.interest.' + k,
                                 default=y + '.depreciation.interest.default')
        plant_life = option_getter(y + '.depreciation.plant_life')
        if interest == 0:
            dep = 1 / plant_life
        else:
            dep = ((interest * (1 + interest) ** plant_life)
                   / (((1 + interest) ** plant_life) - 1))
        return dep
    return get_depreciation_rate
<commit_msg>Use sentinel in AttrDict.get_key<commit_after>"""
Copyright (C) 2013-2015 Stefan Pfenninger.
Licensed under the Apache 2.0 License (see LICENSE file).

utils.py
~~~~~~~~

Various utility functions, particularly the AttrDict class (a subclass
of regular dict) used for managing model configuration.

"""

from contextlib import contextmanager
from io import StringIO
import functools
import os

import numpy as np
import yaml

from . import exceptions

class __Missing(object):
    def __repr__(self):
        return ('MISSING')

    def __nonzero__(self):
        return False

_MISSING = __Missing()

class AttrDict(dict):
    """
    A subclass of ``dict`` with key access by attributes::

        d = AttrDict({'a': 1, 'b': 2})
        d.a == 1  # True

    Includes a range of additional methods to read and write to YAML,
    and to deal with nested keys.

    """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __init__(self, source_dict=None):
        super(AttrDict, self).__init__()
        if isinstance(source_dict, dict):
            self.init_from_dict(source_dict)

    def copy(self):
        """Override copy method so that it returns an AttrDict"""
        return AttrDict(dict(self).copy())

    def init_from_dict(self, d):
        """
        Initialize a new AttrDict from the given dict. Handles any
        nested dicts by turning them into AttrDicts too::

            d = AttrDict({'a': 1, 'b': {'x': 1, 'y': 2}})
            d.b.x == 1  # True

        """
        for k, v in d.items():
            # First, keys must be strings, not ints
            if isinstance(k, int):
                k = str(k)
            # Now, assign to the key, handling nested AttrDicts properly
            if isinstance(v, dict):
                self.set_key(k, AttrDict(v))
            elif isinstance(v, list):
                self.set_key(k, [i if not isinstance(i, dict) else AttrDict(i)
                                 for i in v])
            else:
                self.set_key(k, v)

    @classmethod
    def from_yaml(cls, f, resolve_imports=True):
        """
        Returns an AttrDict initialized from the given path or
        file object ``f``, which must point to a YAML file.

        If ``resolve_imports`` is True, ``import:`` statements are
        resolved recursively, else they are treated like any other key.

        When resolving import statements, anything defined locally
        overrides definitions in the imported file.

        """
        if isinstance(f, str):
            with open(f, 'r') as src:
                loaded = cls(yaml.load(src))
        else:
            loaded = cls(yaml.load(f))
        if resolve_imports and 'import' in loaded:
            for k in loaded['import']:
                imported = cls.from_yaml(relative_path(k, f))
                # loaded is added to imported (i.e. it takes precedence)
                imported.union(loaded)
                loaded = imported
            # 'import' key no longer needed, so we drop it
            loaded.pop('import', None)
        return loaded

    @classmethod
    def from_yaml_string(cls, string):
        """
        Returns an AttrDict initialized from the given string, which
        must be valid YAML.

        """
        return cls(yaml.load(string))

    def set_key(self, key, value):
        """
        Set the given ``key`` to the given ``value``. Handles nested
        keys, e.g.::

            d = AttrDict()
            d.set_key('foo.bar', 1)
            d.foo.bar == 1  # True

        """
        if '.' in key:
            key, remainder = key.split('.', 1)
            try:
                self[key].set_key(remainder, value)
            except KeyError:
                self[key] = AttrDict()
                self[key].set_key(remainder, value)
            except AttributeError:
                if self[key] is None:  # If the value is None, we replace it
                    self[key] = AttrDict()
                    self[key].set_key(remainder, value)
                # Else there is probably something there, and we don't just
                # want to overwrite so stop and warn the user
                else:
                    raise KeyError('Cannot set nested key on non-dict key.')
        else:
            self[key] = value

    def get_key(self, key, default=_MISSING):
        """
        Looks up the given ``key``. Like set_key(), deals with nested
        keys.

        If default is anything but the default _MISSING value, the given
        default is returned if a KeyError is encountered during lookup.

        """
        if '.' in key:
            # Nested key of form "foo.bar"
            key, remainder = key.split('.', 1)
            if default != _MISSING:
                try:
                    value = self[key].get_key(remainder, default)
                except KeyError:
                    # subdict exists, but doesn't contain key
                    return default
                except AttributeError:
                    # key points to non-dict thing, so no get_key attribute
                    return default
            else:
                value = self[key].get_key(remainder)
        else:
            # Single, non-nested key of form "foo"
            if default != _MISSING:
                return self.get(key, default)
            else:
                return self[key]
        return value

    def del_key(self, key):
        """Delete the given key. Properly deals with nested keys."""
        if '.' in key:
            key, remainder = key.split('.', 1)
            try:
                del self[key][remainder]
            except KeyError:
                self[key].del_key(remainder)
        else:
            del self[key]

    def as_dict(self, flat=False):
        """
        Return the AttrDict as a pure dict (with nested dicts if
        necessary).

        """
        if not flat:
            return self.as_dict_nested()
        else:
            return self.as_dict_flat()

    def as_dict_nested(self):
        d = {}
        for k, v in self.items():
            if isinstance(v, AttrDict):
                d[k] = v.as_dict()
            elif isinstance(v, list):
                d[k] = [i if not isinstance(i, AttrDict) else i.as_dict()
                        for i in v]
            else:
                d[k] = v
        return d

    def as_dict_flat(self):
        d = {}
        keys = self.keys_nested()
        for k in keys:
            d[k] = self.get_key(k)
        return d

    def to_yaml(self, path=None, convert_objects=True, **kwargs):
        """
        Saves the AttrDict to the given path as a YAML file.

        If ``path`` is None, returns the YAML string instead.

        Any additional keyword arguments are passed to the YAML writer,
        so can use e.g. ``indent=4`` to override the default of 2.

        ``convert_objects`` (defaults to True) controls whether Numpy
        objects should be converted to regular Python objects, so that
        they are properly displayed in the resulting YAML output.

        """
        if convert_objects:
            result = self.copy()
            for k in result.keys_nested():
                # Convert numpy numbers to regular python ones
                v = result.get_key(k)
                if isinstance(v, np.floating):
                    result.set_key(k, float(v))
                elif isinstance(v, np.integer):
                    result.set_key(k, int(v))
            result = result.as_dict()
        else:
            result = self.as_dict()
        if path is not None:
            with open(path, 'w') as f:
                yaml.dump(result, f, **kwargs)
        else:
            return yaml.dump(result, **kwargs)

    def keys_nested(self, subkeys_as='list'):
        """
        Returns all keys in the AttrDict, sorted, including the keys of
        nested subdicts (which may be either regular dicts or AttrDicts).

        If ``subkeys_as='list'`` (default), then a list of
        all keys is returned, in the form ``['a', 'b.b1', 'b.b2']``.

        If ``subkeys_as='dict'``, a list containing keys and dicts of
        subkeys is returned, in the form ``['a', {'b': ['b1', 'b2']}]``.

        """
        keys = []
        for k, v in sorted(self.items()):
            if isinstance(v, AttrDict) or isinstance(v, dict):
                if subkeys_as == 'list':
                    keys.extend([k + '.' + kk for kk in v.keys_nested()])
                elif subkeys_as == 'dict':
                    keys.append({k: v.keys_nested(subkeys_as=subkeys_as)})
            else:
                keys.append(k)
        return keys

    def union(self, other, allow_override=False, allow_replacement=False):
        """
        Merges the AttrDict in-place with the passed ``other``
        AttrDict. Keys in ``other`` take precedence, and nested keys
        are properly handled.

        If ``allow_override`` is False, a KeyError is raised if
        other tries to redefine an already defined key.

        If ``allow_replacement``, allow "_REPLACE_" key to replace an
        entire sub-dict.

        """
        if allow_replacement:
            WIPE_KEY = '_REPLACE_'
            override_keys = [k for k in other.keys_nested()
                             if WIPE_KEY not in k]
            wipe_keys = [k.split('.' + WIPE_KEY)[0]
                         for k in other.keys_nested()
                         if WIPE_KEY in k]
        else:
            override_keys = other.keys_nested()
            wipe_keys = []
        for k in override_keys:
            if not allow_override and k in self.keys_nested():
                raise KeyError('Key defined twice: {}'.format(k))
            else:
                self.set_key(k, other.get_key(k))
        for k in wipe_keys:
            self.set_key(k, other.get_key(k + '.' + WIPE_KEY))

@contextmanager
def capture_output():
    """
    Capture stdout and stderr output of a wrapped function::

        with capture_output() as out:
            # do things that create stdout or stderr output

    Returns a list with the captured strings: ``[stderr, stdout]``

    """
    import sys
    old_out, old_err = sys.stdout, sys.stderr
    try:
        out = [StringIO(), StringIO()]
        sys.stdout, sys.stderr = out
        yield out
    finally:
        sys.stdout, sys.stderr = old_out, old_err
        out[0] = out[0].getvalue()
        out[1] = out[1].getvalue()

# This used to be a custom function, but as of Python 3.2 we can use
# the built-in lru_cache for simplicity
memoize = functools.lru_cache(maxsize=512)

class memoize_instancemethod(object):
    """
    Cache the return value of a method on a per-instance basis
    (as opposed to a per-class basis like functools.lru_cache does)

    Source: http://code.activestate.com/recipes/577452/

    This class is meant to be used as a decorator of methods. The return
    value from a given method invocation will be cached on the instance
    whose method was invoked. All arguments passed to a method decorated
    with memoize must be hashable.

    If a memoized method is invoked directly on its class the result
    will not be cached. Instead the method will be invoked like a
    static method.

    """
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self.func
        return functools.partial(self, obj)

    def __call__(self, *args, **kw):
        obj = args[0]
        try:
            cache = obj.__cache
        except AttributeError:
            cache = obj.__cache = {}
        key = (self.func, args[1:], frozenset(list(kw.items())))
        try:
            res = cache[key]
        except KeyError:
            res = cache[key] = self.func(*args, **kw)
        return res

def relative_path(path, base_path_file):
    """
    If ``path`` is not absolute, it is interpreted as relative to the
    path of the given ``base_path_file``.

    """
    # Check if base_path_file is a string because it might be an AttrDict
    if not os.path.isabs(path) and isinstance(base_path_file, str):
        path = os.path.join(os.path.dirname(base_path_file), path)
    return path

def option_getter(config_model, data):
    """Returns a get_option() function using the given config_model and data"""
    o = config_model
    d = data

    def get_option(option, x=None, default=None, ignore_inheritance=False):

        def _get_option(opt, fail=False):
            try:
                result = o.get_key('techs.' + opt)
            except KeyError:
                if ignore_inheritance:
                    return _get_option(default, fail)
                # 'ccgt.constraints.s_time' -> 'ccgt', 'constraints.s_time'
                tech, remainder = opt.split('.', 1)
                if ':' in tech:
                    parent = tech.split(':')[0]
                else:
                    # parent = e.g. 'defaults'
                    parent = o.get_key('techs.' + tech + '.parent')
                try:
                    result = _get_option(parent + '.' + remainder, fail)
                except KeyError:
                    e = exceptions.OptionNotSetError
                    if fail:
                        raise e('Failed to read option `{}` '
                                'with given default '
                                '`{}`'.format(option, default))
                    elif default:
                        result = _get_option(default, fail=True)
                    elif tech == 'defaults':
                        raise e('Reached top of inheritance chain '
                                'and no default defined for: '
                                '`{}`'.format(option))
                    else:
                        raise e('Can not get parent for `{}` '
                                'and no default defined '
                                '({}).'.format(tech, option))
            return result

        def _get_location_option(key, location):

            def getter(key, location):
                # NB: KeyErrors raised here are always caught within _get_option
                # so need no further information or handling
                # Raises KeyError if the specific _override column does not exist
                result = d.locations.at[location, '_override.' + key]
                # Also raise KeyError if the result is NaN, i.e. if no
                # location-specific override has been defined
                try:
                    if np.isnan(result):
                        raise KeyError
                # Have to catch this because np.isnan not implemented for strings
                except TypeError:
                    pass
                return result

            while True:
                try:
                    return getter(key, location)
                except KeyError:
                    parent_location = d.locations.at[location, '_within']
                    if parent_location:  # Will be None if no parent
                        return getter(key, parent_location)
                    else:
                        # Once top of "location inheritance" chain reached,
                        # raise KeyError, which will cause the calling function
                        # to fall back to non-location specific settings
                        raise

        if x:
            try:
                result = _get_location_option(option, x)
            # If can't find a location-specific option, fall back to model-wide
            except KeyError:
                result = _get_option(option)
        else:
            result = _get_option(option)
        # Deal with 'inf' settings
        if result == 'inf':
            result = float('inf')
        return result

    return get_option

def cost_getter(option_getter, costs_type='costs'):
    def get_cost(cost, y, k):
        return option_getter(y + '.' + costs_type + '.' + k + '.' + cost,
                             default=y + '.' + costs_type + '.default.' + cost)
    return get_cost

def cost_per_distance_getter(option_getter):
    def get_cost_per_distance(cost, y, k, x):
        try:
            cost_option = y + '.costs_per_distance.' + k + '.' + cost
            cost = option_getter(cost_option)
            per_distance = option_getter(y + '.per_distance')
            distance = option_getter(y + '.distance', x=x)
            distance_cost = cost * (distance / per_distance)
        except exceptions.OptionNotSetError:
            distance_cost = 0
        return distance_cost
    return get_cost_per_distance

def depreciation_getter(option_getter):
    def get_depreciation_rate(y, k):
        interest = option_getter(y + '.depreciation.interest.' + k,
                                 default=y + '.depreciation.interest.default')
        plant_life = option_getter(y + '.depreciation.plant_life')
        if interest == 0:
            dep = 1 / plant_life
        else:
            dep = ((interest * (1 + interest) ** plant_life)
                   / (((1 + interest) ** plant_life) - 1))
        return dep
    return get_depreciation_rate
<|endoftext|>


<commit_before>from gladiator.core import validate as _validate
from functools import wraps

__version__ = "0.1"

def get_version():
    return __version__

def next_version():
    _v = __version__.split('.')
    _v[-1] = str(int(_v[-1]) + 1)
    return '.'.join(_v)

def validate_request(validator):
    def _json_selector(obj, current_selector):
        json_dict = obj.get_json(force=True, silent=True) or {}
        return [(current_selector + ['json'], json_dict)]

    def decorator(func):
        from flask import current_app, request, g

        @wraps(func)
        def inner_func(*args, **kwargs):
            g.request_validation_result = \
                current_app.extensions['gladiator'].validate(
                    validator, request, ctx={
                        'custom_selectors': {
                            'json!': _json_selector
                        }
                    })
            if g.request_validation_result.success:
                return func(*args, **kwargs)
            else:
                return g.request_validation_result.errors
        return inner_func
    return decorator

class Gladiator(object):
    """This class is used to control the Gladiator integration to one
    or more Flask applications."""

    def __init__(self, app=None, default_validation_ctx=None):
        self.default_validation_ctx = default_validation_ctx
        if app is not None:
            self.app = app
            self.init_app(app)
        else:
            self.app = None

    def _default_ctx(self, ctx=None):
        ret = {}
        if self.app.config.get('GLADIATOR_VALIDATION_CTX', None):
            ret.update(self.app.config.get('GLADIATOR_VALIDATION_CTX'))
        if self.default_validation_ctx is not None:
            ret.update(self.default_validation_ctx)
        if ctx is not None:
            ret.update(ctx)
        return ret

    def init_app(self, app):
        app.config.setdefault('GLADIATOR_VALIDATION_CTX', None)

        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['gladiator'] = self

    def validate(self, validator, obj, selector=None, ctx=None, **kw):
        _ctx = self._default_ctx(ctx)
        result = _validate(validator, obj, selector, ctx=_ctx, **kw)
        return result
<commit_msg>small fix<commit_after>from functools import wraps

__version__ = "0.1"

def get_version():
    return __version__

def next_version():
    _v = __version__.split('.')
    _v[-1] = str(int(_v[-1]) + 1)
    return '.'.join(_v)

def validate_request(validator):
    def _json_selector(obj, current_selector):
        json_dict = obj.get_json(force=True, silent=True) or {}
        return [(current_selector + ['json'], json_dict)]

    def decorator(func):
        from flask import current_app, request, g

        @wraps(func)
        def inner_func(*args, **kwargs):
            g.request_validation_result = \
                current_app.extensions['gladiator'].validate(
                    validator, request, ctx={
                        'custom_selectors': {
                            'json!': _json_selector
                        }
                    })
            if g.request_validation_result.success:
                return func(*args, **kwargs)
            else:
                return g.request_validation_result.errors
        return inner_func
    return decorator

class Gladiator(object):
    """This class is used to control the Gladiator integration to one
    or more Flask applications."""

    def __init__(self, app=None, default_validation_ctx=None):
        self.default_validation_ctx = default_validation_ctx
        if app is not None:
            self.app = app
            self.init_app(app)
        else:
            self.app = None

    def _default_ctx(self, ctx=None):
        ret = {}
        if self.app.config.get('GLADIATOR_VALIDATION_CTX', None):
            ret.update(self.app.config.get('GLADIATOR_VALIDATION_CTX'))
        if self.default_validation_ctx is not None:
            ret.update(self.default_validation_ctx)
        if ctx is not None:
            ret.update(ctx)
        return ret

    def init_app(self, app):
        app.config.setdefault('GLADIATOR_VALIDATION_CTX', None)

        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['gladiator'] = self

    def validate(self, validator, obj, selector=None, ctx=None, **kw):
        from gladiator.core import validate as _validate

        _ctx = self._default_ctx(ctx)
        result = _validate(validator, obj, selector, ctx=_ctx, **kw)
        return result
<|endoftext|>


<commit_before>#!/usr/bin/python2.4
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
# $Id$
#
#    Copyright (C) 1999-2006  Keith Dart <keith@kdart.com>
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.

"""
some functions helpful in Python's interactive mode.

I use it like this. Create a file called ~/.pythonrc with the following contents:

----
#!/usr/bin/python
import interactive

----
This modules needs some environment variables to be set. You can also set these
in the .pythonrc file.

PYPS1          - primary interactive prompt
PYPS2          - "more" prompt
PYTHONDOCS     - base directory of the Python HTML documentation set.
PYHISTFILE     - name of the history file (defaults to $HOME/.pythonhist)

Some external program invocations should be defined. These may contain a
'%s' that will be expanded to a file name or url.

XTERM          - How to invoke xterm, e.g. "urxvtc -title Python -name Python -e %s"
EDITOR         - editor to use in the currrent terminal,
VIEWER         - What terminal text viewer (e.g. "/usr/bin/view")
XEDITOR        - text editor for use in the X Windows System.
XVIEWER        - What X text viewer (e.g. "/usr/bin/mousepad")
BROWSER        - HTML browser for X GUI.
CBROWSER       - HTML browser for character terminals.

Now, create some shell aliases in your .bashrc file:

alias py='/usr/bin/env PYTHONSTARTUP=$HOME/.pythonrc python -i'

now, just type "py" at the command line to get a Python interactive mode shell
with enhanced functionality.

Alternatively, you can just 'import interactive' at the original Python prompt.

"""

# stock modules
import sys, os, types
from pprint import pprint
from pycopia.cliutils import *
from pycopia.aid import add2builtin, IF

try:
    import readline
except ImportError:
    # dummy readline to fake the rest of this module
    class Readline:
        def parse_and_bind(self, *args):
            pass
        def read_history_file(self, arg):
            pass
        def write_history_file(self, arg):
            pass
    readline = Readline()

# Big dictionary generated by the 'mkpydocindex' program. Contains
# the Python HTML documentation index dictionary. This is None now, it will be
# imported later if it is used by showdoc().
PYINDEX = None
PYDOCINDEXFILE = os.path.join("/", "var", "tmp", "pydocindex.py")

gtktools = None
if os.environ.has_key("DISPLAY"):
    try:
        from pycopia import gtktools
    except:
        pass # no gtk? oh well...

__all__ = ['info', 'run_config', 'pyterm', 'xterm', 'edit', 'get_editor',
'exec_editor', 'open_url', 'open_file', 'get_pydocindex', 'get_doc_urls',
'showdoc']
from pycopia.textutils import *
from pycopia import textutils
__all__.extend([n for n in dir(textutils) if n[0] != "_" and callable(getattr(textutils, n))])
del textutils

# update environ with what's in the rc file.
if sys.platform == "win32":
    RCFILE = os.path.join(os.path.expandvars("$USERPROFILE"), "_pyinteractiverc")
else:
    RCFILE = os.path.join(os.path.expandvars("$HOME"), ".pyinteractiverc")

try:
    env = {}
    execfile(RCFILE, env, env)
except:
    ex, val, tb = sys.exc_info()
    print >>sys.stderr, "warning: could not read config file:", RCFILE
    print >>sys.stderr, val
else:
    for name, val in env.items():
        if type(val) is str:
            os.environ[name] = val
del env

PYTHON = os.environ.get("PYTHONBIN", "python") # use for alternate interpreter
sys.ps1 = os.environ.get("PYPS1", "Python> ")
sys.ps2 = os.environ.get("PYPS2", ".more.> ")

def info(obj=None):
    """Print some helpful information about the given object. Usually the
    docstring.  If no parameter given then provide some info on Python itself."""
    if obj is None:
        print "Python keywords:"
        import keyword
        print pprint(keyword.kwlist)
        print
        plist = [] ; clist = []
        for bi_object in __builtins__.values():
            if callable(bi_object):
                if type(bi_object) is types.ClassType:
                    clist.append(bi_object.__name__)
                elif type(bi_object) is types.FunctionType:
                    plist.append(bi_object.func_code.co_name)
        plist.sort() ; clist.sort()
        print "Python built-in functions:"
        pprint(plist)
        print
        print "Python built-in exceptions:"
        pprint(clist)
        print
    elif hasattr(obj, "__doc__") and obj.__doc__ is not None:
            print "Documentation for %s :\n" % (obj.__name__)
            print obj.__doc__
    elif type(obj) is types.ModuleType:
        pprint(dir(obj))
    elif type(obj) is types.ClassType:
        pprint(dir(obj))
    elif type(obj) is types.InstanceType:
        pprint(dir(obj))
        pprint(dir(obj.__class__))
    return ""

def run_config(cfstring, param):
    if not cfstring:
        print >>sys.stderr, "No command string defined to run %s." % (param)
        return
    try:
        cmd = cfstring % param
    except TypeError: # no %s in cfstring, so just stick the param on the end
        cmd = "%s %s" % (cfstring, param)
    return os.system(cmd)

def pyterm(filename="", interactive=1):
    cmd = "%s %s %s " % (PYTHON, IF(interactive, "-i", ""), filename)
    if os.environ.has_key("DISPLAY"):
        return run_config(os.environ.get("XTERM"), cmd)
    else:
        return os.system(cmd)

def xterm(cmd="/bin/sh"):
    if os.environ.has_key("DISPLAY"):
        return run_config(os.environ.get("XTERM"), cmd)
    else:
        return os.system(cmd)

def edit(modname):
    """
Opens the $XEDITOR with the given module source file (if found).
    """
    filename = find_source_file(modname)
    if filename:
        ed = get_editor()
        return run_config(ed, filename)
    else:
        print >>sys.stderr, "Could not find source to %s." % modname

def view(modname):
    """
Opens the $[X]VIEWER with the given module source file (if found).
    """
    filename = find_source_file(modname)
    if filename:
        ed = get_viewer()
        return run_config(ed, filename)
    else:
        print >>sys.stderr, "Could not find source to %s." % modname

def get_editor():
    if os.environ.has_key("DISPLAY"):
        ed = os.environ.get("XEDITOR", None)
    else:
        ed = os.environ.get("EDITOR", None)
    if ed is None:
        ed = get_input("Use which editor?", "/bin/vi")
    return ed

def get_viewer():
    if os.environ.has_key("DISPLAY"):
        ed = os.environ.get("XVIEWER", None)
    else:
        ed = os.environ.get("VIEWER", None)
    if ed is None:
        ed = get_input("Use which viewer?", "/usr/bin/view")
    return ed

def exec_editor(*names):
    """Runs your configured editor on a supplied list of files. Uses exec,
there is no return!"""
    ed = get_editor()
    if ed.find("/") >= 0:
        os.execv(ed, (ed,)+names)
    else:
        os.execvp(ed, (ed,)+names)

def open_url(url):
    """Opens the given URL in an external viewer. """
    if os.environ.has_key("DISPLAY"):
        return run_config(os.environ.get("BROWSER"), url)
    else:
        return run_config(os.environ.get("CBROWSER"), url)

def open_file(filename):
    return open_url("file://"+filename)

def get_pydocindex():
    global PYINDEX # from pydocindex.py file
    if not PYINDEX: # lazy import
        try:
            execfile(PYDOCINDEXFILE, globals())
        except IOError:
            print >>sys.stdout, "no file pydocindex.py. Run mkpydocindex to generate it."
            return None

def get_doc_urls(keyword):
    global PYINDEX
    if not PYINDEX: # lazy import
        get_pydocindex()
    if PYINDEX:
        return PYINDEX.get(keyword, None)
    else:
        return None

def showdoc(object, chooser=None):
    """Opens your browser with the HTML documentation for the Python object.
Choose from a list if more than one document is found."""
    if not chooser:
        if gtktools:
            chooser = gtktools.list_picker
        else:
            chooser = choose

    objtype = type(object)
    if objtype is str:
        name = object
    elif objtype is types.ModuleType or objtype is types.BuiltinFunctionType:
        name = object.__name__
    else:
        print >>sys.stderr, "showdoc: can't determine object name"
        return
    docuri = get_doc_urls(name)
    if docuri:
        if len(docuri) == 1:
            open_url("file:///%s/%s" % (os.environ.get("PYTHONDOCS"), docuri[0]))

        else:
            uri = chooser(docuri)
            if uri:
                open_url("file:///%s/%s" % (os.environ.get("PYTHONDOCS"), uri))
    else:
        print >>sys.stderr, "showdoc: No documentation found."

def mydisplayhook(obj):
    pprint(obj)
    setattr(sys.modules["__main__"], "_", obj)
    #__builtins__._ =  obj
setattr(sys.modules["__main__"], "_", None)

sys.displayhook = mydisplayhook

# Add some functions useful for interactive use to builtins.
# This is done to provide better interactive functionality without
# cluttering the __main__ namespace.
def _add_builtins(names=__all__):
    for name in names:
        add2builtin(name, getattr(sys.modules[__name__], name))

_add_builtins()

if sys.platform == "win32":
    _default_hist = os.path.join(os.environ["USERPROFILE"], "_pythonhist")
else:
    _default_hist = os.path.join(os.environ["HOME"], ".pythonhist")
PYHISTFILE = os.environ.get("PYHISTFILE", _default_hist)

try:
    import rlcompleter2
    rlcompleter2.setup(PYHISTFILE, verbose=0)
except ImportError:
    import rlcompleter
    import atexit
    try:
        readline.read_history_file(PYHISTFILE)
    except IOError:
        pass
    def savehist():
        readline.write_history_file(PYHISTFILE)
    atexit.register( savehist )
    readline.parse_and_bind("tab: complete")

## readline key bindings
##readline.parse_and_bind("tab: menu-complete")
#readline.parse_and_bind("tab: complete")
#readline.parse_and_bind('"?": possible-completions')
readline.parse_and_bind('"\M-h": "help()\n"')
readline.parse_and_bind('"\eOP": "help()\n"')
readline.parse_and_bind('"\M-?": possible-completions')
readline.parse_and_bind('"\M-f": dump-functions')
readline.parse_and_bind('"\M-v": dump-variables')
readline.parse_and_bind('"\M-m": dump-macros')
# additional readline options
##readline.parse_and_bind("set editing-mode vi")
#readline.parse_and_bind("set show-all-if-ambiguous on")
#readline.parse_and_bind("set meta-flag on")
#readline.parse_and_bind("set input-meta on")
#readline.parse_and_bind("set output-meta on")
#readline.parse_and_bind("set convert-meta off")
#

<commit_msg>Improve user experience of interactive module.<commit_after>#!/usr/bin/python2.4
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
# $Id$
#
#    Copyright (C) 1999-2006  Keith Dart <keith@kdart.com>
#
#    This library is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation; either
#    version 2.1 of the License, or (at your option) any later version.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.

"""
some functions helpful in Python's interactive mode.

I use it like this. Create a file called ~/.pythonrc with the following contents:

----
#!/usr/bin/python
import interactive

----
This modules needs some environment variables to be set. You can also set these
in the .pythonrc file.

PYPS1          - primary interactive prompt
PYPS2          - "more" prompt
PYTHONDOCS     - base directory of the Python HTML documentation set.
PYHISTFILE     - name of the history file (defaults to $HOME/.pythonhist)

Some external program invocations should be defined. These may contain a
'%s' that will be expanded to a file name or url.

XTERM          - How to invoke xterm, e.g. "urxvtc -title Python -name Python -e %s"
EDITOR         - editor to use in the currrent terminal,
VIEWER         - What terminal text viewer (e.g. "/usr/bin/view")
XEDITOR        - text editor for use in the X Windows System.
XVIEWER        - What X text viewer (e.g. "/usr/bin/mousepad")
BROWSER        - HTML browser for X GUI.
CBROWSER       - HTML browser for character terminals.

Now, create some shell aliases in your .bashrc file:

alias py='/usr/bin/env PYTHONSTARTUP=$HOME/.pythonrc python -i'

now, just type "py" at the command line to get a Python interactive mode shell
with enhanced functionality.

Alternatively, you can just 'import interactive' at the original Python prompt.

"""

import sys, os, types
import rlcompleter
import atexit
from pprint import pprint

from pycopia.cliutils import *
from pycopia.aid import add2builtin, IF

try:
    import readline
except ImportError:
    # dummy readline to fake the rest of this module
    class Readline:
        def parse_and_bind(self, *args):
            pass
        def read_history_file(self, arg):
            pass
        def write_history_file(self, arg):
            pass
    readline = Readline()

# Big dictionary generated by the 'mkpydocindex' program. Contains
# the Python HTML documentation index dictionary. This is None now, it will be
# imported later if it is used by showdoc().
PYINDEX = None
PYDOCINDEXFILE = os.path.join("/", "var", "tmp", "pydocindex.py")

gtktools = None
if os.environ.has_key("DISPLAY"):
    try:
        from pycopia import gtktools
    except:
        pass # no gtk? oh well...

__all__ = ['info', 'run_config', 'pyterm', 'xterm', 'edit', 'get_editor',
'exec_editor', 'open_url', 'open_file', 'get_pydocindex', 'get_doc_urls',
'showdoc']
from pycopia.textutils import *
from pycopia import textutils
__all__.extend([n for n in dir(textutils) if n[0] != "_" and callable(getattr(textutils, n))])
del textutils

# update environ with what's in the rc file.
if sys.platform == "win32":
    RCFILE = os.path.join(os.path.expandvars("$USERPROFILE"), "_pyinteractiverc")
else:
    RCFILE = os.path.join(os.path.expandvars("$HOME"), ".pyinteractiverc")

# Create default rc file if it doesn't exist.
if not os.path.exists(RCFILE):
    rcfo = open(RCFILE, "w")
    rcfo.write("""#!/usr/bin/python
# some configuration stuff primarily for the 'interactive' module.

PYPS1 = "Python> "
PYPS2 = ".more.> "

PYTHONDOCS = "/usr/share/doc/python-docs-2.6.2-r1/html"
# programs
#XTERM = "urxvtc -title Python -name Python -e %s"
XTERM = "xterm -title Python -name Python -e %s"
EDITOR = '/usr/bin/vim'
XEDITOR = "/usr/bin/gvim"
VIEWER = "/usr/bin/view"
XVIEWER = "/usr/bin/gview"
BROWSER = '/usr/bin/epiphany "%s"'
CBROWSER = '/usr/bin/links "%s"'
""")
    rcfo.close()
    del rcfo

try:
    env = {}
    execfile(RCFILE, env, env)
except:
    ex, val, tb = sys.exc_info()
    print >>sys.stderr, "warning: could not read config file:", RCFILE
    print >>sys.stderr, val
else:
    for name, val in env.items():
        if type(val) is str:
            os.environ[name] = val
del env

PYTHON = os.environ.get("PYTHONBIN", "python") # set PYTHONBIN for alternate interpreter
sys.ps1 = os.environ.get("PYPS1", "Python> ")
sys.ps2 = os.environ.get("PYPS2", ".more.> ")

def info(obj=None):
    """Print some helpful information about the given object. Usually the
    docstring.  If no parameter given then provide some info on Python itself."""
    if obj is None:
        print "Python keywords:"
        import keyword
        print pprint(keyword.kwlist)
        print
        plist = [] ; clist = []
        for bi_object in __builtins__.values():
            if callable(bi_object):
                if type(bi_object) is types.ClassType:
                    clist.append(bi_object.__name__)
                elif type(bi_object) is types.FunctionType:
                    plist.append(bi_object.func_code.co_name)
        plist.sort() ; clist.sort()
        print "Python built-in functions:"
        pprint(plist)
        print
        print "Python built-in exceptions:"
        pprint(clist)
        print
    elif hasattr(obj, "__doc__") and obj.__doc__ is not None:
            print "Documentation for %s :\n" % (obj.__name__)
            print obj.__doc__
    elif type(obj) is types.ModuleType:
        pprint(dir(obj))
    elif type(obj) is types.ClassType:
        pprint(dir(obj))
    elif type(obj) is types.InstanceType:
        pprint(dir(obj))
        pprint(dir(obj.__class__))
    return ""

def run_config(cfstring, param):
    if not cfstring:
        print >>sys.stderr, "No command string defined to run %s." % (param)
        return
    try:
        cmd = cfstring % param
    except TypeError: # no %s in cfstring, so just stick the param on the end
        cmd = "%s %s" % (cfstring, param)
    return os.system(cmd)

def pyterm(filename="", interactive=1):
    cmd = "%s %s %s " % (PYTHON, IF(interactive, "-i", ""), filename)
    if os.environ.has_key("DISPLAY"):
        return run_config(os.environ.get("XTERM"), cmd)
    else:
        return os.system(cmd)

def xterm(cmd="/bin/sh"):
    if os.environ.has_key("DISPLAY"):
        return run_config(os.environ.get("XTERM"), cmd)
    else:
        return os.system(cmd)

def edit(modname):
    """
Opens the $XEDITOR with the given module source file (if found).
    """
    filename = find_source_file(modname)
    if filename:
        ed = get_editor()
        return run_config(ed, filename)
    else:
        print >>sys.stderr, "Could not find source to %s." % modname

def view(modname):
    """
Opens the $[X]VIEWER with the given module source file (if found).
    """
    filename = find_source_file(modname)
    if filename:
        ed = get_viewer()
        return run_config(ed, filename)
    else:
        print >>sys.stderr, "Could not find source to %s." % modname

def get_editor():
    if os.environ.has_key("DISPLAY"):
        ed = os.environ.get("XEDITOR", None)
    else:
        ed = os.environ.get("EDITOR", None)
    if ed is None:
        ed = get_input("Use which editor?", "/bin/vi")
    return ed

def get_viewer():
    if os.environ.has_key("DISPLAY"):
        ed = os.environ.get("XVIEWER", None)
    else:
        ed = os.environ.get("VIEWER", None)
    if ed is None:
        ed = get_input("Use which viewer?", "/usr/bin/view")
    return ed

def exec_editor(*names):
    """Runs your configured editor on a supplied list of files. Uses exec,
there is no return!"""
    ed = get_editor()
    if ed.find("/") >= 0:
        os.execv(ed, (ed,)+names)
    else:
        os.execvp(ed, (ed,)+names)

def open_url(url):
    """Opens the given URL in an external viewer. """
    if os.environ.has_key("DISPLAY"):
        return run_config(os.environ.get("BROWSER"), url)
    else:
        return run_config(os.environ.get("CBROWSER"), url)

def open_file(filename):
    return open_url("file://"+filename)

def get_pydocindex():
    global PYINDEX # from pydocindex.py file
    if not PYINDEX: # lazy import
        try:
            execfile(PYDOCINDEXFILE, globals())
        except IOError:
            print >>sys.stdout, "no file pydocindex.py. Run mkpydocindex to generate it."
            return None

def get_doc_urls(keyword):
    global PYINDEX
    if not PYINDEX: # lazy import
        get_pydocindex()
    if PYINDEX:
        return PYINDEX.get(keyword, None)
    else:
        return None

def showdoc(object, chooser=None):
    """Opens your browser with the HTML documentation for the Python object.
Choose from a list if more than one document is found."""
    if not chooser:
        if gtktools:
            chooser = gtktools.list_picker
        else:
            chooser = choose

    objtype = type(object)
    if objtype is str:
        name = object
    elif objtype is types.ModuleType or objtype is types.BuiltinFunctionType:
        name = object.__name__
    else:
        print >>sys.stderr, "showdoc: can't determine object name"
        return
    docuri = get_doc_urls(name)
    if docuri:
        if len(docuri) == 1:
            open_url("file:///%s/%s" % (os.environ.get("PYTHONDOCS"), docuri[0]))

        else:
            uri = chooser(docuri)
            if uri:
                open_url("file:///%s/%s" % (os.environ.get("PYTHONDOCS"), uri))
    else:
        print >>sys.stderr, "showdoc: No documentation found."

def mydisplayhook(obj):
    pprint(obj)
    setattr(sys.modules["__main__"], "_", obj)
setattr(sys.modules["__main__"], "_", None)

sys.displayhook = mydisplayhook

# Add some functions useful for interactive use to builtins.
# This is done to provide better interactive functionality without
# cluttering the __main__ namespace.
def _add_builtins(names=__all__):
    for name in names:
        add2builtin(name, getattr(sys.modules[__name__], name))

_add_builtins()

if sys.platform == "win32":
    _default_hist = os.path.join(os.environ["USERPROFILE"], "_pythonhist")
else:
    _default_hist = os.path.join(os.environ["HOME"], ".pythonhist")
PYHISTFILE = os.environ.get("PYHISTFILE", _default_hist)

try:
    readline.read_history_file(PYHISTFILE)
except IOError:
    pass
def savehist():
    readline.write_history_file(PYHISTFILE)
atexit.register( savehist )
readline.parse_and_bind("tab: complete")

## readline key bindings
##readline.parse_and_bind("tab: menu-complete")
#readline.parse_and_bind('"?": possible-completions')
readline.parse_and_bind('"\M-?": possible-completions')
#readline.parse_and_bind('"\M-h": "help()\n"')
#readline.parse_and_bind('"\eOP": "help()\n"')
#readline.parse_and_bind('"\M-f": dump-functions')
#readline.parse_and_bind('"\M-v": dump-variables')
#readline.parse_and_bind('"\M-m": dump-macros')
# additional readline options
##readline.parse_and_bind("set editing-mode vi")
#readline.parse_and_bind("set show-all-if-ambiguous on")
#readline.parse_and_bind("set meta-flag on")
#readline.parse_and_bind("set input-meta on")
#readline.parse_and_bind("set output-meta on")
#readline.parse_and_bind("set convert-meta off")
#

<|endoftext|>


<commit_before># encoding: utf8
# TODO, this specifically:
# - allow prefixing (in the Camel obj?) otherwise assume local, don't require the leading !
# - figure out namespacing and urls and whatever the christ
# - document exactly which ones we support (i.e., yaml supports)
# TODO, general:
# - DWIM -- block style except for very short sequences (if at all?), quotey style for long text...

# TODO BEFORE PUBLISHING:
# - /must/ strip the leading ! from tag names and allow giving a prefix (difficulty: have to do %TAG directive manually)
# - /must/ remove type arg from loader  (would be nice to have as documentation, but is useless?  blurgh)
# - /must/ figure out what happens with subclasses, and block if necessary for now, or make opt-in (but then, what happens with the tag, to indicate the subclass?)
# - /must/ work on python 2
# - /must/ use python 3 semantics for strings
# - better versioning story, interop with no version somehow, what is the use case for versionless?  assuming it will never change?  imo should require version
# - should write some docs, both on camel and on yaml

# TODO test a versioned thing
# TODO how does versioning interact with unversioned...?  maybe /require/ version, don't default to None

# TODO from alex gaynor's talk, starting around 24m in:
# - class is deleted (if it's useless, just return a dummy value)
# - attribute changes

# TODO minor questions and gripes:
# - should this complain if there are overlapping definitions?
# - how do we handle subclasses?  how does yaml?  what if there are conflicts?
# - dumper and loader could be easily made to work on methods...  i think...  in py3

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import collections
import functools
from io import StringIO
import types

import yaml

try:
    from yaml import CSafeDumper as SafeDumper
    from yaml import CSafeLoader as SafeLoader
except ImportError:
    from yaml import SafeDumper
    from yaml import SafeLoader

YAML_TAG_PREFIX = 'tag:yaml.org,2002:'

class CamelDumper(SafeDumper):
    """Subclass of yaml's `SafeDumper` that scopes representers to the
    instance, rather than to the particular class, because damn.
    """
    def __init__(self, *args, **kwargs):
        # TODO this isn't quite good enough; pyyaml still escapes anything
        # outside the BMP
        kwargs.setdefault('allow_unicode', True)
        super(CamelDumper, self).__init__(*args, **kwargs)
        self.yaml_representers = SafeDumper.yaml_representers.copy()
        self.yaml_multi_representers = SafeDumper.yaml_multi_representers.copy()

    def add_representer(self, data_type, representer):
        self.yaml_representers[data_type] = representer

    def add_multi_representer(self, data_type, representer):
        self.yaml_multi_representers[data_type] = representer

class CamelLoader(SafeLoader):
    """Subclass of yaml's `SafeLoader` that scopes constructors to the
    instance, rather than to the particular class, because damn.
    """
    def __init__(self, *args, **kwargs):
        super(CamelLoader, self).__init__(*args, **kwargs)
        self.yaml_constructors = SafeLoader.yaml_constructors.copy()
        self.yaml_multi_constructors = SafeLoader.yaml_multi_constructors.copy()
        self.yaml_implicit_resolvers = SafeLoader.yaml_implicit_resolvers.copy()

    def add_constructor(self, data_type, constructor):
        self.yaml_constructors[data_type] = constructor

    def add_multi_constructor(self, data_type, constructor):
        self.yaml_multi_constructors[data_type] = constructor

    def add_implicit_resolver(self, tag, regexp, first):
        if first is None:
            first = [None]
        for ch in first:
            self.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))

    def add_path_resolver(self, *args, **kwargs):
        # This API is non-trivial and claims to be experimental and unstable
        raise NotImplementedError

class Camel(object):
    def __init__(self, registries=()):
        self.registries = (STANDARD_TYPES,) + tuple(registries)

    def make_dumper(self, stream):
        dumper = CamelDumper(stream, default_flow_style=False)
        for registry in self.registries:
            registry.inject_dumpers(dumper)
        return dumper

    def dump(self, data):
        stream = StringIO()
        dumper = self.make_dumper(stream)
        dumper.open()
        dumper.represent(data)
        dumper.close()
        return stream.getvalue()

    def make_loader(self, stream):
        loader = CamelLoader(stream)
        for registry in self.registries:
            registry.inject_loaders(loader)
        return loader

    def load(self, data):
        stream = StringIO(data)
        loader = self.make_loader(stream)
        obj = loader.get_data()
        if loader.check_node():
            raise RuntimeError("Multiple documents found in stream; use load_all")
        return obj

    def load_first(self, data):
        stream = StringIO(data)
        loader = self.make_loader(stream)
        return loader.get_data()

    def load_all(self, data):
        stream = StringIO(data)
        loader = self.make_loader(stream)
        while loader.check_node():
            yield loader.get_data()

class CamelRegistry(object):
    frozen = False

    def __init__(self):
        self.dumpers = {}
        self.loaders = {}

    def freeze(self):
        self.frozen = True

    # Dumping

    def dumper(self, cls, tag, version=None):
        if self.frozen:
            raise RuntimeError("Can't add to a frozen registry")

        assert '#' not in tag
        if version is not None:
            assert isinstance(version, (int, float))
            tag = "{}#{}".format(tag, version)

        def decorator(f):
            self.dumpers[tag] = cls, functools.partial(self.run_representer, f, tag)
            return f

        return decorator

    def run_representer(self, representer, tag, dumper, data):
        canon_value = representer(data)
        # Note that we /do not/ support subclasses of the built-in types here,
        # to avoid complications from returning types that have their own
        # custom representers
        canon_type = type(canon_value)
        # TODO this gives no control over flow_style, style, and implicit.  do
        # we intend to figure it out ourselves?
        if canon_type in (dict, collections.OrderedDict):
            return dumper.represent_mapping(tag, canon_value, flow_style=False)
        elif canon_type in (tuple, list):
            return dumper.represent_sequence(tag, canon_value, flow_style=False)
        # TODO py2 compat?  long, something about str/unicode?
        elif canon_type in (int, float, bool, str, type(None)):
            return dumper.represent_scalar(tag, canon_value)
        else:
            raise TypeError(
                "Representers must return native YAML types, but the representer "
                "for {!r} returned {!r}, which is of type {!r}"
                .format(data, canon_value, canon_type))

    def inject_dumpers(self, dumper):
        for tag, (cls, representer) in self.dumpers.items():
            dumper.add_representer(cls, representer)

    # Loading
    # TODO implement "upgrader", which upgrades from one version to another

    def loader(self, cls, tag, version=None):
        if self.frozen:
            raise RuntimeError("Can't add to a frozen registry")

        # TODO is there any good point to passing in cls
        # TODO this is copy/pasted, and hokey besides
        assert '#' not in tag
        if version is not None:
            assert isinstance(version, (int, float))
            tag = "{}#{}".format(tag, version)

        def decorator(f):
            self.loaders[tag] = cls, functools.partial(self.run_constructor, f)
            return f

        return decorator

    def run_constructor(self, constructor, loader, node):
        if isinstance(node, yaml.ScalarNode):
            data = loader.construct_scalar(node)
        elif isinstance(node, yaml.SequenceNode):
            data = loader.construct_sequence(node, deep=True)
        elif isinstance(node, yaml.MappingNode):
            data = loader.construct_mapping(node, deep=True)
        else:
            raise TypeError("Not a primitive node: {!r}".format(node))
        return constructor(data)

    def inject_loaders(self, loader):
        for tag, (cls, constructor) in self.loaders.items():
            loader.add_constructor(tag, constructor)

# TODO "raw" loaders and dumpers that get access to loader/dumper and deal with
# raw nodes?
# TODO multi_constructor, multi_representer, implicit_resolver

# YAML's "language-independent types" — not builtins, but supported with
# standard !! tags.  Most of them are built into pyyaml, but OrderedDict is
# curiously overlooked.  Loaded first by default into every Camel object.
# Ref: http://yaml.org/type/
# TODO pyyaml supports tags like !!python/list; do we care?
STANDARD_TYPES = CamelRegistry()

@STANDARD_TYPES.dumper(frozenset, YAML_TAG_PREFIX + 'set')
def _dump_frozenset(data):
    return dict.fromkeys(data)

@STANDARD_TYPES.dumper(collections.OrderedDict, YAML_TAG_PREFIX + 'omap')
def _dump_ordered_dict(data):
    pairs = []
    for key, value in data.items():
        pairs.append({key: value})
    return pairs

@STANDARD_TYPES.loader(collections.OrderedDict, YAML_TAG_PREFIX + 'omap')
def _load_ordered_dict(data):
    # TODO assert only single kv per thing
    return collections.OrderedDict(
        next(iter(datum.items())) for datum in data
    )

# Extra Python types that don't have native YAML equivalents, but that PyYAML
# supports with !!python/foo tags.  Dumping them isn't supported by default,
# but loading them is, since there's no good reason for it not to be.
# A couple of these dumpers override builtin type support.  For example, tuples
# are dumped as lists by default, but this registry will dump them as
# !!python/tuple.
PYTHON_TYPES = CamelRegistry()

@PYTHON_TYPES.dumper(tuple, YAML_TAG_PREFIX + 'python/tuple')
def _dump_tuple(data):
    return list(data)

@STANDARD_TYPES.loader(tuple, YAML_TAG_PREFIX + 'python/tuple')
def _load_tuple(data):
    return tuple(data)

@PYTHON_TYPES.dumper(complex, YAML_TAG_PREFIX + 'python/complex')
def _dump_complex(data):
    ret = repr(data)
    # Complex numbers become (1+2j), but the parens are superfluous
    if ret[0] == '(' and ret[-1] == ')':
        return ret[1:-1]
    else:
        return ret

@STANDARD_TYPES.loader(complex, YAML_TAG_PREFIX + 'python/complex')
def _load_complex(data):
    return complex(data)

@PYTHON_TYPES.dumper(frozenset, YAML_TAG_PREFIX + 'python/frozenset')
def _dump_complex(data):
    try:
        return list(sorted(data))
    except TypeError:
        return list(data)

@STANDARD_TYPES.loader(frozenset, YAML_TAG_PREFIX + 'python/frozenset')
def _load_complex(data):
    return frozenset(data)

if hasattr(types, 'SimpleNamespace'):
    @PYTHON_TYPES.dumper(types.SimpleNamespace, YAML_TAG_PREFIX + 'python/namespace')
    def _dump_simple_namespace(data):
        return data.__dict__

    @STANDARD_TYPES.loader(types.SimpleNamespace, YAML_TAG_PREFIX + 'python/namespace')
    def _load_simple_namespace(data):
        return types.SimpleNamespace(**data)

STANDARD_TYPES.freeze()
PYTHON_TYPES.freeze()
<commit_msg>Delete more TODOs<commit_after># encoding: utf8
# TODO, this specifically:
# - allow prefixing (in the Camel obj?) otherwise assume local, don't require the leading !
# - figure out namespacing and urls and whatever the christ
# - document exactly which ones we support (i.e., yaml supports)
# TODO, general:
# - DWIM -- block style except for very short sequences (if at all?), quotey style for long text...

# TODO BEFORE PUBLISHING:
# - /must/ strip the leading ! from tag names and allow giving a prefix (difficulty: have to do %TAG directive manually)
# - /must/ remove type arg from loader  (would be nice to have as documentation, but is useless?  blurgh)
# - /must/ work on python 2
# - /must/ use python 3 semantics for strings
# - better versioning story, interop with no version somehow, what is the use case for versionless?  assuming it will never change?  imo should require version
# - should write some docs, both on camel and on yaml

# TODO from alex gaynor's talk, starting around 24m in:
# - class is deleted (if it's useless, just return a dummy value)
# - attribute changes

# TODO little niceties
# - complain if there are overlapping definitions within the same registry?
# - opt-in thing for subclasses, i think pyyaml has some facilities for this even (multi)
# - dumper and loader could be easily made to work on methods...  i think...  in py3

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import collections
import functools
from io import StringIO
import types

import yaml

try:
    from yaml import CSafeDumper as SafeDumper
    from yaml import CSafeLoader as SafeLoader
except ImportError:
    from yaml import SafeDumper
    from yaml import SafeLoader

YAML_TAG_PREFIX = 'tag:yaml.org,2002:'

class CamelDumper(SafeDumper):
    """Subclass of yaml's `SafeDumper` that scopes representers to the
    instance, rather than to the particular class, because damn.
    """
    def __init__(self, *args, **kwargs):
        # TODO this isn't quite good enough; pyyaml still escapes anything
        # outside the BMP
        kwargs.setdefault('allow_unicode', True)
        super(CamelDumper, self).__init__(*args, **kwargs)
        self.yaml_representers = SafeDumper.yaml_representers.copy()
        self.yaml_multi_representers = SafeDumper.yaml_multi_representers.copy()

    def add_representer(self, data_type, representer):
        self.yaml_representers[data_type] = representer

    def add_multi_representer(self, data_type, representer):
        self.yaml_multi_representers[data_type] = representer

class CamelLoader(SafeLoader):
    """Subclass of yaml's `SafeLoader` that scopes constructors to the
    instance, rather than to the particular class, because damn.
    """
    def __init__(self, *args, **kwargs):
        super(CamelLoader, self).__init__(*args, **kwargs)
        self.yaml_constructors = SafeLoader.yaml_constructors.copy()
        self.yaml_multi_constructors = SafeLoader.yaml_multi_constructors.copy()
        self.yaml_implicit_resolvers = SafeLoader.yaml_implicit_resolvers.copy()

    def add_constructor(self, data_type, constructor):
        self.yaml_constructors[data_type] = constructor

    def add_multi_constructor(self, data_type, constructor):
        self.yaml_multi_constructors[data_type] = constructor

    def add_implicit_resolver(self, tag, regexp, first):
        if first is None:
            first = [None]
        for ch in first:
            self.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))

    def add_path_resolver(self, *args, **kwargs):
        # This API is non-trivial and claims to be experimental and unstable
        raise NotImplementedError

class Camel(object):
    def __init__(self, registries=()):
        self.registries = (STANDARD_TYPES,) + tuple(registries)

    def make_dumper(self, stream):
        dumper = CamelDumper(stream, default_flow_style=False)
        for registry in self.registries:
            registry.inject_dumpers(dumper)
        return dumper

    def dump(self, data):
        stream = StringIO()
        dumper = self.make_dumper(stream)
        dumper.open()
        dumper.represent(data)
        dumper.close()
        return stream.getvalue()

    def make_loader(self, stream):
        loader = CamelLoader(stream)
        for registry in self.registries:
            registry.inject_loaders(loader)
        return loader

    def load(self, data):
        stream = StringIO(data)
        loader = self.make_loader(stream)
        obj = loader.get_data()
        if loader.check_node():
            raise RuntimeError("Multiple documents found in stream; use load_all")
        return obj

    def load_first(self, data):
        stream = StringIO(data)
        loader = self.make_loader(stream)
        return loader.get_data()

    def load_all(self, data):
        stream = StringIO(data)
        loader = self.make_loader(stream)
        while loader.check_node():
            yield loader.get_data()

class CamelRegistry(object):
    frozen = False

    def __init__(self):
        self.dumpers = {}
        self.loaders = {}

    def freeze(self):
        self.frozen = True

    # Dumping

    def dumper(self, cls, tag, version=None):
        if self.frozen:
            raise RuntimeError("Can't add to a frozen registry")

        assert '#' not in tag
        if version is not None:
            assert isinstance(version, (int, float))
            tag = "{}#{}".format(tag, version)

        def decorator(f):
            self.dumpers[tag] = cls, functools.partial(self.run_representer, f, tag)
            return f

        return decorator

    def run_representer(self, representer, tag, dumper, data):
        canon_value = representer(data)
        # Note that we /do not/ support subclasses of the built-in types here,
        # to avoid complications from returning types that have their own
        # custom representers
        canon_type = type(canon_value)
        # TODO this gives no control over flow_style, style, and implicit.  do
        # we intend to figure it out ourselves?
        if canon_type in (dict, collections.OrderedDict):
            return dumper.represent_mapping(tag, canon_value, flow_style=False)
        elif canon_type in (tuple, list):
            return dumper.represent_sequence(tag, canon_value, flow_style=False)
        # TODO py2 compat?  long, something about str/unicode?
        elif canon_type in (int, float, bool, str, type(None)):
            return dumper.represent_scalar(tag, canon_value)
        else:
            raise TypeError(
                "Representers must return native YAML types, but the representer "
                "for {!r} returned {!r}, which is of type {!r}"
                .format(data, canon_value, canon_type))

    def inject_dumpers(self, dumper):
        for tag, (cls, representer) in self.dumpers.items():
            dumper.add_representer(cls, representer)

    # Loading
    # TODO implement "upgrader", which upgrades from one version to another

    def loader(self, cls, tag, version=None):
        if self.frozen:
            raise RuntimeError("Can't add to a frozen registry")

        # TODO is there any good point to passing in cls
        # TODO this is copy/pasted, and hokey besides
        assert '#' not in tag
        if version is not None:
            assert isinstance(version, (int, float))
            tag = "{}#{}".format(tag, version)

        def decorator(f):
            self.loaders[tag] = cls, functools.partial(self.run_constructor, f)
            return f

        return decorator

    def run_constructor(self, constructor, loader, node):
        if isinstance(node, yaml.ScalarNode):
            data = loader.construct_scalar(node)
        elif isinstance(node, yaml.SequenceNode):
            data = loader.construct_sequence(node, deep=True)
        elif isinstance(node, yaml.MappingNode):
            data = loader.construct_mapping(node, deep=True)
        else:
            raise TypeError("Not a primitive node: {!r}".format(node))
        return constructor(data)

    def inject_loaders(self, loader):
        for tag, (cls, constructor) in self.loaders.items():
            loader.add_constructor(tag, constructor)

# TODO "raw" loaders and dumpers that get access to loader/dumper and deal with
# raw nodes?
# TODO multi_constructor, multi_representer, implicit_resolver

# YAML's "language-independent types" — not builtins, but supported with
# standard !! tags.  Most of them are built into pyyaml, but OrderedDict is
# curiously overlooked.  Loaded first by default into every Camel object.
# Ref: http://yaml.org/type/
# TODO pyyaml supports tags like !!python/list; do we care?
STANDARD_TYPES = CamelRegistry()

@STANDARD_TYPES.dumper(frozenset, YAML_TAG_PREFIX + 'set')
def _dump_frozenset(data):
    return dict.fromkeys(data)

@STANDARD_TYPES.dumper(collections.OrderedDict, YAML_TAG_PREFIX + 'omap')
def _dump_ordered_dict(data):
    pairs = []
    for key, value in data.items():
        pairs.append({key: value})
    return pairs

@STANDARD_TYPES.loader(collections.OrderedDict, YAML_TAG_PREFIX + 'omap')
def _load_ordered_dict(data):
    # TODO assert only single kv per thing
    return collections.OrderedDict(
        next(iter(datum.items())) for datum in data
    )

# Extra Python types that don't have native YAML equivalents, but that PyYAML
# supports with !!python/foo tags.  Dumping them isn't supported by default,
# but loading them is, since there's no good reason for it not to be.
# A couple of these dumpers override builtin type support.  For example, tuples
# are dumped as lists by default, but this registry will dump them as
# !!python/tuple.
PYTHON_TYPES = CamelRegistry()

@PYTHON_TYPES.dumper(tuple, YAML_TAG_PREFIX + 'python/tuple')
def _dump_tuple(data):
    return list(data)

@STANDARD_TYPES.loader(tuple, YAML_TAG_PREFIX + 'python/tuple')
def _load_tuple(data):
    return tuple(data)

@PYTHON_TYPES.dumper(complex, YAML_TAG_PREFIX + 'python/complex')
def _dump_complex(data):
    ret = repr(data)
    # Complex numbers become (1+2j), but the parens are superfluous
    if ret[0] == '(' and ret[-1] == ')':
        return ret[1:-1]
    else:
        return ret

@STANDARD_TYPES.loader(complex, YAML_TAG_PREFIX + 'python/complex')
def _load_complex(data):
    return complex(data)

@PYTHON_TYPES.dumper(frozenset, YAML_TAG_PREFIX + 'python/frozenset')
def _dump_complex(data):
    try:
        return list(sorted(data))
    except TypeError:
        return list(data)

@STANDARD_TYPES.loader(frozenset, YAML_TAG_PREFIX + 'python/frozenset')
def _load_complex(data):
    return frozenset(data)

if hasattr(types, 'SimpleNamespace'):
    @PYTHON_TYPES.dumper(types.SimpleNamespace, YAML_TAG_PREFIX + 'python/namespace')
    def _dump_simple_namespace(data):
        return data.__dict__

    @STANDARD_TYPES.loader(types.SimpleNamespace, YAML_TAG_PREFIX + 'python/namespace')
    def _load_simple_namespace(data):
        return types.SimpleNamespace(**data)

STANDARD_TYPES.freeze()
PYTHON_TYPES.freeze()
<|endoftext|>