<commit_before>import sys

import six

from .core import _validate_int, _validate_bool

_undefined = object()

def _try_contextualize_exception(context):
    """
    Attempts to re-raise a `TypeError` or `ValueError` with a description of
    the context.

    If the original error does not match the expected form, will simply return
    without raising anything.
    """
    exc_type, exc_value, exc_traceback = sys.exc_info()

    if exc_type not in (TypeError, ValueError):
        # No safe way to extend the message for subclasses.
        return

    # Check that the exception has been properly constructed.  The
    # documentation requires that :exception:`TypeError`s and
    # :exception:`ValueError`s are constructed with a single,
    # string argument, but this is not enforced anywhere.
    if len(exc_value.args) != 1:
        return

    if not isinstance(exc_value.args[0], str):
        return

    message = "{context}:  {message}".format(
        context=context, message=exc_value.args[0],
    )

    six.raise_from(exc_type(message), exc_value)

def _validate_list(
    value, validator=None,
    min_length=None, max_length=None,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, list):
        raise TypeError((
            "expected 'list', value is of type {cls!r}"
        ).format(cls=type(value).__name__))

    if min_length is not None and len(value) < min_length:
        raise ValueError((
            "expected at least {expected} elements, "
            "but list contains only {actual}"
        ).format(expected=min_length, actual=len(value)))

    if max_length is not None and len(value) > max_length:
        raise ValueError((
            "expected at most {expected} elements, "
            "but list contains {actual}"
        ).format(expected=max_length, actual=len(value)))

    if validator is not None:
        for index, item in enumerate(value):
            try:
                validator(item)
            except (TypeError, ValueError):
                _try_contextualize_exception(
                    "invalid item at position {index}".format(index=index),
                )
                raise

def validate_list(
    value=_undefined,
    validator=None,
    min_length=None, max_length=None,
    required=True,
):
    """
    Validator to check a list and all of its contents.

    :param list value:
        The array to be validated.
    :param func validator:
        A function to be called on each value in the list to check that it is
        valid.
    :param int min_length:
        The minimum acceptable length for the list.  If `None`, the minimum
        length is not checked.
    :param int max_length:
        The maximum acceptable length for the list.  If `None`, the maximum
        length is not checked.
    :param bool required:
        Whether the value can be `None`.  Defaults to `True`.
     """
    _validate_int(max_length, min_value=0, required=False)
    # The max_value check here is fine.  If max_length is None then there is no
    # cap on the min_length.  We do validate max_length first though.
    _validate_int(
        min_length, min_value=0, max_value=max_length, required=False,
    )
    _validate_bool(required)

    def validate(value):
        _validate_list(
            value, validator=validator,
            min_length=min_length, max_length=max_length,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def _validate_set(
    value, validator=None,
    min_length=None, max_length=None,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, set):
        raise TypeError((
            "expected 'set', but value is of type {cls!r}"
        ).format(cls=type(value).__name__))

    if min_length is not None and len(value) < min_length:
        raise ValueError((
            "expected at least {expected} entries, "
            "but set contains only {actual}"
        ).format(expected=min_length, actual=len(value)))

    if max_length is not None and len(value) > max_length:
        raise ValueError((
            "expected at most {expected} entries, "
            "but set contains {actual}"
        ).format(expected=max_length, actual=len(value)))

    if validator is not None:
        for item in value:
            validator(item)

def validate_set(
    value=_undefined,
    validator=None,
    min_length=None, max_length=None,
    required=True,
):
    """
    Validator to check a set and all entries in it.

    :param set value:
        The set to be validated.
    :param func validator:
        A function to be called on each entry in the set to check that it is
        valid.
    :param int min_length:
        The minimum acceptable number of entries in the set.  If `None`, the
        minimum size is not checked.
    :param int max_length:
        The maximum acceptable number of entries in the set.  If `None`, the
        maximum size is not checked.
    :param bool required:
        Whether the value can be `None`.  Defaults to `True`.
     """
    _validate_int(max_length, min_value=0, required=False)
    # The max_value check here is fine.  If max_length is None then there is no
    # cap on the min_length.  We do validate max_length first though.
    _validate_int(
        min_length, min_value=0, max_value=max_length, required=False,
    )
    _validate_bool(required)

    def validate(value):
        _validate_set(
            value, validator=validator,
            min_length=min_length, max_length=max_length,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def _validate_mapping(
    value,
    key_validator=None, value_validator=None,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, dict):
        raise TypeError((
            "expected 'dict', but value is of type {cls!r}"
        ).format(cls=value.__class__.__name__))

    for item_key, item_value in value.items():
        if key_validator is not None:
            try:
                key_validator(item_key)
            except (TypeError, ValueError):
                _try_contextualize_exception(
                    "invalid key {key!r}".format(key=item_key),
                )
                raise

        if value_validator is not None:
            try:
                value_validator(item_value)
            except (TypeError, ValueError):
                _try_contextualize_exception(
                    "invalid value for key {key!r}".format(key=item_key),
                )
                raise

def validate_mapping(
    value=_undefined,
    key_validator=None, value_validator=None,
    required=True,
):
    """
    Validates a dictionary representing a simple mapping from keys of one type
    to values of another.

    :param dict value:
        The value to be validated.
    :param func key_validator:
        Optional function to be call to check each of the keys in the
        dictionary.
    :param func value_validator:
        Optional function to be call to check each of the values in the
        dictionary.
    :param bool required:
        Whether the value can't be `None`. Defaults to `True`.
    """
    _validate_bool(required)

    def validate(value):
        _validate_mapping(
            value,
            key_validator=key_validator,
            value_validator=value_validator,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def _validate_structure(
    value,
    schema=None, allow_extra=False,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, dict):
        raise TypeError((
            "expected 'dict' but value is of type {cls!r}"
        ).format(cls=value.__class__.__name__))

    if schema is not None:
        for key, validator in schema.items():
            if key not in value:
                raise ValueError((
                    "dictionary missing expected key: {key!r}"
                ).format(key=key, dictionary=value))

            try:
                validator(value[key])
            except (TypeError, ValueError):
                _try_contextualize_exception(
                    "invalid value for key {key!r}".format(key=key),
                )
                raise

        if not allow_extra and set(schema) != set(value):
            raise ValueError((
                "dictionary contains unexpected keys: {unexpected}"
            ).format(
                unexpected=', '.join(
                    repr(unexpected)
                    for unexpected in set(value) - set(schema)
                )
            ))

def validate_structure(
    value=_undefined,
    schema=None, allow_extra=False,
    required=True,
):
    """
    Validates a structured dictionary, with value types depending on the key,
    checking it against an optional schema.

    The schema should be a dictionary, with keys corresponding to the expected
    keys in `value`, but with the values replaced by functions which will be
    called to with the corresponding value in the input.

    A simple example:

        validator = validate_structure(schema={
            'id': validate_key(kind='Model'),
            'count': validate_int(min=0),
        })
        validator({'id': self.key, 'count': self.count})

    :param dict value:
        The value to be validated.
    :param dict schema:
        The schema against which the value should be checked.
    :param bool allow_extra:
        Set to `True` to ignore extra keys.
    :param bool required:
        Whether the value can't be `None`. Defaults to True.
    """
    _validate_structure(schema, schema=None, required=False)
    _validate_bool(allow_extra)
    _validate_bool(required)

    # Make a copy of the schema to make sure it won't be mutated under us.
    if schema is not None:
        schema = dict(schema)

    def validate(value):
        _validate_structure(
            value,
            schema=schema, allow_extra=allow_extra,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def validate_tuple(
    value=_undefined,
    schema=None,
    required=True,
):
    raise NotImplementedError()

def validate_enum(
    value=_undefined,
    kind=None,
    required=True,
):
    raise NotImplementedError()
<commit_msg>Add context to `KeyError`s<commit_after>import sys

import six

from .core import _validate_int, _validate_bool

_undefined = object()

def _try_contextualize_exception(context):
    """
    Attempts to re-raise a `TypeError` or `ValueError` with a description of
    the context.

    If the original error does not match the expected form, will simply return
    without raising anything.
    """
    exc_type, exc_value, exc_traceback = sys.exc_info()

    if exc_type not in (TypeError, ValueError, KeyError):
        # No safe way to extend the message for subclasses.
        return

    # Check that the exception has been properly constructed.  The
    # documentation requires that :exception:`TypeError`s and
    # :exception:`ValueError`s are constructed with a single,
    # string argument, but this is not enforced anywhere.
    if len(exc_value.args) != 1:
        return

    if not isinstance(exc_value.args[0], str):
        return

    message = "{context}:  {message}".format(
        context=context, message=exc_value.args[0],
    )

    six.raise_from(exc_type(message), exc_value)

def _validate_list(
    value, validator=None,
    min_length=None, max_length=None,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, list):
        raise TypeError((
            "expected 'list', value is of type {cls!r}"
        ).format(cls=type(value).__name__))

    if min_length is not None and len(value) < min_length:
        raise ValueError((
            "expected at least {expected} elements, "
            "but list contains only {actual}"
        ).format(expected=min_length, actual=len(value)))

    if max_length is not None and len(value) > max_length:
        raise ValueError((
            "expected at most {expected} elements, "
            "but list contains {actual}"
        ).format(expected=max_length, actual=len(value)))

    if validator is not None:
        for index, item in enumerate(value):
            try:
                validator(item)
            except (TypeError, ValueError, KeyError):
                _try_contextualize_exception(
                    "invalid item at position {index}".format(index=index),
                )
                raise

def validate_list(
    value=_undefined,
    validator=None,
    min_length=None, max_length=None,
    required=True,
):
    """
    Validator to check a list and all of its contents.

    :param list value:
        The array to be validated.
    :param func validator:
        A function to be called on each value in the list to check that it is
        valid.
    :param int min_length:
        The minimum acceptable length for the list.  If `None`, the minimum
        length is not checked.
    :param int max_length:
        The maximum acceptable length for the list.  If `None`, the maximum
        length is not checked.
    :param bool required:
        Whether the value can be `None`.  Defaults to `True`.
     """
    _validate_int(max_length, min_value=0, required=False)
    # The max_value check here is fine.  If max_length is None then there is no
    # cap on the min_length.  We do validate max_length first though.
    _validate_int(
        min_length, min_value=0, max_value=max_length, required=False,
    )
    _validate_bool(required)

    def validate(value):
        _validate_list(
            value, validator=validator,
            min_length=min_length, max_length=max_length,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def _validate_set(
    value, validator=None,
    min_length=None, max_length=None,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, set):
        raise TypeError((
            "expected 'set', but value is of type {cls!r}"
        ).format(cls=type(value).__name__))

    if min_length is not None and len(value) < min_length:
        raise ValueError((
            "expected at least {expected} entries, "
            "but set contains only {actual}"
        ).format(expected=min_length, actual=len(value)))

    if max_length is not None and len(value) > max_length:
        raise ValueError((
            "expected at most {expected} entries, "
            "but set contains {actual}"
        ).format(expected=max_length, actual=len(value)))

    if validator is not None:
        for item in value:
            validator(item)

def validate_set(
    value=_undefined,
    validator=None,
    min_length=None, max_length=None,
    required=True,
):
    """
    Validator to check a set and all entries in it.

    :param set value:
        The set to be validated.
    :param func validator:
        A function to be called on each entry in the set to check that it is
        valid.
    :param int min_length:
        The minimum acceptable number of entries in the set.  If `None`, the
        minimum size is not checked.
    :param int max_length:
        The maximum acceptable number of entries in the set.  If `None`, the
        maximum size is not checked.
    :param bool required:
        Whether the value can be `None`.  Defaults to `True`.
     """
    _validate_int(max_length, min_value=0, required=False)
    # The max_value check here is fine.  If max_length is None then there is no
    # cap on the min_length.  We do validate max_length first though.
    _validate_int(
        min_length, min_value=0, max_value=max_length, required=False,
    )
    _validate_bool(required)

    def validate(value):
        _validate_set(
            value, validator=validator,
            min_length=min_length, max_length=max_length,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def _validate_mapping(
    value,
    key_validator=None, value_validator=None,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, dict):
        raise TypeError((
            "expected 'dict', but value is of type {cls!r}"
        ).format(cls=value.__class__.__name__))

    for item_key, item_value in value.items():
        if key_validator is not None:
            try:
                key_validator(item_key)
            except (TypeError, ValueError, KeyError):
                _try_contextualize_exception(
                    "invalid key {key!r}".format(key=item_key),
                )
                raise

        if value_validator is not None:
            try:
                value_validator(item_value)
            except (TypeError, ValueError, KeyError):
                _try_contextualize_exception(
                    "invalid value for key {key!r}".format(key=item_key),
                )
                raise

def validate_mapping(
    value=_undefined,
    key_validator=None, value_validator=None,
    required=True,
):
    """
    Validates a dictionary representing a simple mapping from keys of one type
    to values of another.

    :param dict value:
        The value to be validated.
    :param func key_validator:
        Optional function to be call to check each of the keys in the
        dictionary.
    :param func value_validator:
        Optional function to be call to check each of the values in the
        dictionary.
    :param bool required:
        Whether the value can't be `None`. Defaults to `True`.
    """
    _validate_bool(required)

    def validate(value):
        _validate_mapping(
            value,
            key_validator=key_validator,
            value_validator=value_validator,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def _validate_structure(
    value,
    schema=None, allow_extra=False,
    required=True,
):
    if value is None:
        if not required:
            return
        raise TypeError("required value is None")

    if not isinstance(value, dict):
        raise TypeError((
            "expected 'dict' but value is of type {cls!r}"
        ).format(cls=value.__class__.__name__))

    if schema is not None:
        for key, validator in schema.items():
            if key not in value:
                raise ValueError((
                    "dictionary missing expected key: {key!r}"
                ).format(key=key, dictionary=value))

            try:
                validator(value[key])
            except (TypeError, ValueError, KeyError):
                _try_contextualize_exception(
                    "invalid value for key {key!r}".format(key=key),
                )
                raise

        if not allow_extra and set(schema) != set(value):
            raise ValueError((
                "dictionary contains unexpected keys: {unexpected}"
            ).format(
                unexpected=', '.join(
                    repr(unexpected)
                    for unexpected in set(value) - set(schema)
                )
            ))

def validate_structure(
    value=_undefined,
    schema=None, allow_extra=False,
    required=True,
):
    """
    Validates a structured dictionary, with value types depending on the key,
    checking it against an optional schema.

    The schema should be a dictionary, with keys corresponding to the expected
    keys in `value`, but with the values replaced by functions which will be
    called to with the corresponding value in the input.

    A simple example:

        validator = validate_structure(schema={
            'id': validate_key(kind='Model'),
            'count': validate_int(min=0),
        })
        validator({'id': self.key, 'count': self.count})

    :param dict value:
        The value to be validated.
    :param dict schema:
        The schema against which the value should be checked.
    :param bool allow_extra:
        Set to `True` to ignore extra keys.
    :param bool required:
        Whether the value can't be `None`. Defaults to True.
    """
    _validate_structure(schema, schema=None, required=False)
    _validate_bool(allow_extra)
    _validate_bool(required)

    # Make a copy of the schema to make sure it won't be mutated under us.
    if schema is not None:
        schema = dict(schema)

    def validate(value):
        _validate_structure(
            value,
            schema=schema, allow_extra=allow_extra,
            required=required,
        )

    if value is not _undefined:
        validate(value)
    else:
        return validate

def validate_tuple(
    value=_undefined,
    schema=None,
    required=True,
):
    raise NotImplementedError()

def validate_enum(
    value=_undefined,
    kind=None,
    required=True,
):
    raise NotImplementedError()
<|endoftext|>


<commit_before>from ConfigParser import ConfigParser
from os.path import isfile
from pacha.database import Worker

def options(config=None):
    """Instead of calling ConfigParser all over the place
    we gather, read, parse and return valid configuration
    values for any pacha log.utility here, config should
    always be a file object or None and config_options
    always returns a dictionary with values"""

    # If all fails we will always have default values
    configuration = defaults()

    # Options comming from the config file have
    # longer names, hence the need to map them correctly
    opt_mapper = {
            'pacha.frequency':'frequency',
            'pacha.master':'master',
            'pacha.host':'host',
            'pacha.ssh.user':'ssh_user',
            'pacha.ssh.port':'ssh_port',
            'pacha.hosts.path': 'hosts_path',
            'pacha.hg.autocorrect': 'hg_autocorrect',
            'pacha.log.enable': 'log_enable',
            'pacha.log.path' : 'log_path',
            'pacha.log.level':'log_level',
            'pacha.log.format':'log_format',
            'pacha.log.datefmt':'log_datefmt'
            }

    try:
        if config == None or isfile(config) == False:
            configuration = defaults()
            return configuration

    except TypeError:
        if type(config) is dict:
            configuration = defaults(config)

    else:
        try:
            converted_opts = {}
            parser = ConfigParser()
            parser.read(config)
            file_options = parser.defaults()

            # we are not sure about the section so we
            # read the whole thing and loop through the items
            for key, value in opt_mapper.items():
                try:
                    file_value = file_options[key]
                    converted_opts[value] = file_value

                except KeyError:
                    pass # we will fill any empty values later with config_defaults
            try:
                configuration = defaults(converted_opts)
            except Exception, e:
                print "Couldn't map configuration: %s" % e

        except Exception, e:
            pass
            print "Couldn't map configuration: %s" % e

    return configuration

def defaults(config=None):
    """From the config dictionary it checks missing values and
    adds the defaul ones for them if any"""
    if config == None:
        config = {}
    defaults = {
            'frequency': 60,
            'master': 'False',
            'host': 'localhost',
            'ssh_user': 'root',
            'ssh_port': 22,
            'hosts_path': '/opt/pacha',
            'hg_autocorrect': 'True',
            'log_enable': 'False',
            'log_path': 'False',
            'log_level': 'DEBUG',
            'log_format': '%(asctime)s %(levelname)s %(name)s %(message)s',
            'log_datefmt' : '%H:%M:%S'
            }

    for key in defaults:
        try:
            config[key]
        except KeyError:
            config[key] = defaults[key]
    return config

def stored_conf():
    db = Worker()
    db_config = db.get_full_config()
    return defaults(db_config)
<commit_msg>the defaults configurator will check for empty values and not only non existent ones<commit_after>from ConfigParser import ConfigParser
from os.path import isfile
from pacha.database import Worker

def options(config=None):
    """Instead of calling ConfigParser all over the place
    we gather, read, parse and return valid configuration
    values for any pacha log.utility here, config should
    always be a file object or None and config_options
    always returns a dictionary with values"""

    # If all fails we will always have default values
    configuration = defaults()

    # Options comming from the config file have
    # longer names, hence the need to map them correctly
    opt_mapper = {
            'pacha.frequency':'frequency',
            'pacha.master':'master',
            'pacha.host':'host',
            'pacha.ssh.user':'ssh_user',
            'pacha.ssh.port':'ssh_port',
            'pacha.hosts.path': 'hosts_path',
            'pacha.hg.autocorrect': 'hg_autocorrect',
            'pacha.log.enable': 'log_enable',
            'pacha.log.path' : 'log_path',
            'pacha.log.level':'log_level',
            'pacha.log.format':'log_format',
            'pacha.log.datefmt':'log_datefmt'
            }

    try:
        if config == None or isfile(config) == False:
            configuration = defaults()
            return configuration

    except TypeError:
        if type(config) is dict:
            configuration = defaults(config)

    else:
        try:
            converted_opts = {}
            parser = ConfigParser()
            parser.read(config)
            file_options = parser.defaults()

            # we are not sure about the section so we
            # read the whole thing and loop through the items
            for key, value in opt_mapper.items():
                try:
                    file_value = file_options[key]
                    converted_opts[value] = file_value

                except KeyError:
                    pass # we will fill any empty values later with config_defaults
            try:
                configuration = defaults(converted_opts)
            except Exception, e:
                print "Couldn't map configuration: %s" % e

        except Exception, e:
            pass
            print "Couldn't map configuration: %s" % e

    return configuration

def defaults(config=None):
    """From the config dictionary it checks missing values and
    adds the defaul ones for them if any"""
    if config == None:
        config = {}
    defaults = {
            'frequency': 60,
            'master': 'False',
            'host': 'localhost',
            'ssh_user': 'root',
            'ssh_port': 22,
            'hosts_path': '/opt/pacha',
            'hg_autocorrect': 'True',
            'log_enable': 'False',
            'log_path': 'False',
            'log_level': 'DEBUG',
            'log_format': '%(asctime)s %(levelname)s %(name)s %(message)s',
            'log_datefmt' : '%H:%M:%S'
            }

    for key in defaults:
        try:
            config[key]
            if config[key] == '':
                config[key] = defaults[key]
        except KeyError:
            config[key] = defaults[key]
    return config

def stored_conf():
    db = Worker()
    db_config = db.get_full_config()
    return defaults(db_config)

<|endoftext|>


<commit_before>"""
bdist_conda

"""
from __future__ import (print_function, division, unicode_literals,
    absolute_import)

from collections import defaultdict
import configparser

from distutils.command.install import install
from distutils.errors import DistutilsOptionError, DistutilsGetoptError
from distutils.dist import Distribution

from conda.compat import StringIO, string_types
from conda.lock import Locked
import conda.config
from conda.cli.common import spec_from_line
from conda_build.metadata import MetaData
from conda_build import config, build, pypi

# TODO: Add support for all the options that conda build has

class CondaDistribution(Distribution):
    """
    Distribution subclass that supports bdist_conda options

    This class is required if you want to pass any bdist_conda specific
    options to setup().  To use, set distclass=CondaDistribution in setup().

    **NOTE**: If you use setuptools, you must import setuptools before
    importing distutils.commands.bdist_conda.

    Options that can be passed to setup() (must include
    distclass=CondaDistribution):

    - conda_buildnum: The build number. Defaults to 0. Can be overridden on
      the command line with the --buildnum flag.

    - conda_buildstr: The build string. Default is generated automatically
      from the Python version, NumPy version if relevant, and the build
      number, like py34_0.

    - conda_import_tests: Whether to automatically run import tests. The
      default is True, which runs import tests for the all the modules in
      "packages". Also allowed are False, which runs no tests, or a list of
      module names to be tested on import.

    - conda_command_tests: Command line tests to run. Default is True, which
      runs ``command --help`` for each ``command`` in the console_scripts and
      gui_scripts entry_points. Also allowed are False, which doesn't run any
      command tests, or a list of command tests to run.

    - conda_binary_relocation: Whether binary files should be made relocatable
      (using install_name_tool on OS X or patchelf on Linux). The default is
      True. See the "making # packages relocatable" section in the conda build
      documentation for more information on this.

    - conda_preserve_egg_dir: Whether to preserve the egg directory as
      installed by setuptools.  The default is True if the package depends on
      setuptools or has a setuptools entry_points other than console_scripts
      and gui_scripts.

    - conda_features: List of features for the package. See the features
      section of the conda build documentation for more information about
      features in conda.

    - conda_track_features: List of features that this package should track
      (enable when installed).  See the features section of the conda build
      documentation for more information about features in conda.

    Command line options:

    --buildnum: Set the build number. Defaults to the conda_buildnum passed to
      setup(), or 0. Overrides any conda_buildnum passed to setup().

    """
    # Unfortunately, there's no way to warn the users that they need to use
    # distclass=CondaDistribution when they try to use a conda option to
    # setup(). Distribution.__init__ will just print a warning when it sees an
    # attr it doesn't recognize, and then it is discarded.

    # attr: default
    conda_attrs = {
        'conda_buildnum': 0,
        'conda_buildstr': None,
        'conda_import_tests': True,
        'conda_command_tests': True,
        'conda_binary_relocation': True,
        'conda_preserve_egg_dir': None,
        'conda_features': None,
        'conda_track_features': None,
        }

    def __init__(self, attrs=None):
        given_attrs = {}
        # We need to remove the attrs so that Distribution.__init__ doesn't
        # warn about them.
        if attrs:
            for attr in self.conda_attrs:
                if attr in attrs:
                    given_attrs[attr] = attrs.pop(attr)

        super(CondaDistribution, self).__init__(attrs)

        for attr in self.conda_attrs:
            setattr(self.metadata, attr, given_attrs.get(attr, self.conda_attrs[attr]))

class bdist_conda(install):
    description = "create a conda package"

    def initialize_options(self):
        super(bdist_conda, self).initialize_options()
        self.buildnum = None

    def finalize_options(self):
        opt_dict = self.distribution.get_option_dict('install')
        if self.prefix:
            raise DistutilsOptionError("--prefix is not allowed")
        opt_dict['prefix'] = ("bdist_conda", config.build_prefix)
        super(bdist_conda, self).finalize_options()

    def run(self):
        # Make sure the metadata has the conda attributes, even if the
        # distclass isn't CondaDistribution. We primarily do this to simplify
        # the code below.

        metadata = self.distribution.metadata

        for attr in CondaDistribution.conda_attrs:
            if not hasattr(metadata, attr):
                setattr(metadata, attr,
                    CondaDistribution.conda_attrs[attr])

        # The command line takes precedence
        if self.buildnum is not None:
            metadata.conda_buildnum = self.buildnum

        with Locked(config.croot):
            d = defaultdict(dict)
            # Needs to be lowercase
            d['package']['name'] = metadata.name
            d['package']['version'] = metadata.version
            d['build']['number'] = metadata.conda_buildnum

            # MetaData does the auto stuff if the build string is None
            d['build']['string'] = metadata.conda_buildstr

            d['build']['binary_relocation'] = metadata.conda_binary_relocation
            d['build']['preserve_egg_dir'] = metadata.conda_preserve_egg_dir
            d['build']['features'] = metadata.conda_features
            d['build']['track_features'] = metadata.conda_track_features

            # XXX: I'm not really sure if it is correct to combine requires
            # and install_requires
            d['requirements']['run'] = d['requirements']['build'] = \
                [spec_from_line(i) for i in
                    (metadata.requires or []) +
                    (getattr(self.distribution, 'install_requires', []) or
                        [])] + ['python']
            if hasattr(self.distribution, 'tests_require'):
                # A lot of packages use extras_require['test'], but
                # tests_require is the one that is officially supported by
                # setuptools.
                d['test']['requires'] = [spec_from_line(i) for i in
                    self.distribution.tests_require or []]

            d['about']['home'] = metadata.url
            # Don't worry about classifiers. This isn't skeleton pypi. We
            # don't need to make this work with random stuff in the wild. If
            # someone writes their setup.py wrong and this doesn't work, it's
            # their fault.
            d['about']['license'] = metadata.license
            d['about']['summary'] = self.distribution.description

            # This is similar logic from conda skeleton pypi
            entry_points = getattr(self.distribution, 'entry_points', [])
            if entry_points:
                if isinstance(entry_points, string_types):
                    # makes sure it is left-shifted
                    newstr = "\n".join(x.strip() for x in
                        entry_points.split('\n'))
                    c = configparser.ConfigParser()
                    entry_points = {}
                    try:
                        c.readfp(StringIO(newstr))
                    except Exception as err:
                        # This seems to be the best error here
                        raise DistutilsGetoptError("ERROR: entry-points not understood: " + str(err) + "\nThe string was" + newstr)
                    else:
                        for section in config.sections():
                            if section in ['console_scripts', 'gui_scripts']:
                                value = ['%s=%s' % (option, config.get(section, option))
                                         for option in config.options(section)]
                                entry_points[section] = value
                            else:
                                # Make sure setuptools is added as a dependency below
                                entry_points[section] = None

                if not isinstance(entry_points, dict):
                    raise DistutilsGetoptError("ERROR: Could not add entry points. They were:\n" + entry_points)
                else:
                    cs = entry_points.get('console_scripts', [])
                    gs = entry_points.get('gui_scripts', [])
                    # We have *other* kinds of entry-points so we need
                    # setuptools at run-time
                    if not cs and not gs and len(entry_points) > 1:
                        d['requirements']['run'].append('setuptools')
                        d['requirements']['build'].append('setuptools')
                    entry_list = cs + gs
                    if gs and conda.config.platform == 'osx':
                        d['build']['osx_is_app'] = True
                    if len(cs + gs) != 0:
                        d['build']['entry_points'] = entry_list
                        if metadata.conda_command_tests == True:
                            d['test']['commands'] = pypi.make_entry_tests(entry_list)

            if 'setuptools' in d['requirements']['run']:
                d['build']['preserve_egg_dir'] = True

            if metadata.conda_import_tests:
                if metadata.conda_import_tests == True:
                    d['test']['imports'] = [metadata.name]
                    if self.distribution.packages:
                        d['test']['imports'].extend(self.distribution.packages)
                else:
                    d['test']['imports'] = metadata.conda_import_tests

            if (metadata.conda_command_tests and not
                isinstance(metadata.conda_command_tests,
                bool)):
                d['test']['commands'] = metadata.conda_command_tests

            d = dict(d)
            m = MetaData.fromdict(d)
            # Shouldn't fail, but do you really trust the code above?
            m.check_fields()
            build.build(m, post=False)
            # Do the install
            super(bdist_conda, self).run()
            build.build(m, post=True)
            build.test(m)

# Distutils looks for user_options on the class (not instance).  It also
# requires that it is an instance of list. So we do this here because we want
# to keep the options from the superclass (and because I don't feel like
# making a metaclass just to make this work).

bdist_conda.user_options.extend([
    ('buildnum=', None, '''The build number of
    the conda package. Defaults to 0, or the conda_buildnum specified in the
    setup() function. The command line flag overrides the option to
    setup().'''),
    ])
<commit_msg>Add --binstar-upload to bdist_conda<commit_after>"""
bdist_conda

"""
from __future__ import (print_function, division, unicode_literals,
    absolute_import)

from collections import defaultdict
import configparser

from distutils.command.install import install
from distutils.errors import DistutilsOptionError, DistutilsGetoptError
from distutils.dist import Distribution

from conda.compat import StringIO, string_types
from conda.lock import Locked
import conda.config
from conda.cli.common import spec_from_line
from conda_build.metadata import MetaData
from conda_build import config, build, pypi
from conda_build.main_build import handle_binstar_upload

# TODO: Add support for all the options that conda build has

class CondaDistribution(Distribution):
    """
    Distribution subclass that supports bdist_conda options

    This class is required if you want to pass any bdist_conda specific
    options to setup().  To use, set distclass=CondaDistribution in setup().

    **NOTE**: If you use setuptools, you must import setuptools before
    importing distutils.commands.bdist_conda.

    Options that can be passed to setup() (must include
    distclass=CondaDistribution):

    - conda_buildnum: The build number. Defaults to 0. Can be overridden on
      the command line with the --buildnum flag.

    - conda_buildstr: The build string. Default is generated automatically
      from the Python version, NumPy version if relevant, and the build
      number, like py34_0.

    - conda_import_tests: Whether to automatically run import tests. The
      default is True, which runs import tests for the all the modules in
      "packages". Also allowed are False, which runs no tests, or a list of
      module names to be tested on import.

    - conda_command_tests: Command line tests to run. Default is True, which
      runs ``command --help`` for each ``command`` in the console_scripts and
      gui_scripts entry_points. Also allowed are False, which doesn't run any
      command tests, or a list of command tests to run.

    - conda_binary_relocation: Whether binary files should be made relocatable
      (using install_name_tool on OS X or patchelf on Linux). The default is
      True. See the "making # packages relocatable" section in the conda build
      documentation for more information on this.

    - conda_preserve_egg_dir: Whether to preserve the egg directory as
      installed by setuptools.  The default is True if the package depends on
      setuptools or has a setuptools entry_points other than console_scripts
      and gui_scripts.

    - conda_features: List of features for the package. See the features
      section of the conda build documentation for more information about
      features in conda.

    - conda_track_features: List of features that this package should track
      (enable when installed).  See the features section of the conda build
      documentation for more information about features in conda.

    Command line options:

    --buildnum: Set the build number. Defaults to the conda_buildnum passed to
      setup(), or 0. Overrides any conda_buildnum passed to setup().

    """
    # Unfortunately, there's no way to warn the users that they need to use
    # distclass=CondaDistribution when they try to use a conda option to
    # setup(). Distribution.__init__ will just print a warning when it sees an
    # attr it doesn't recognize, and then it is discarded.

    # attr: default
    conda_attrs = {
        'conda_buildnum': 0,
        'conda_buildstr': None,
        'conda_import_tests': True,
        'conda_command_tests': True,
        'conda_binary_relocation': True,
        'conda_preserve_egg_dir': None,
        'conda_features': None,
        'conda_track_features': None,
        }

    def __init__(self, attrs=None):
        given_attrs = {}
        # We need to remove the attrs so that Distribution.__init__ doesn't
        # warn about them.
        if attrs:
            for attr in self.conda_attrs:
                if attr in attrs:
                    given_attrs[attr] = attrs.pop(attr)

        super(CondaDistribution, self).__init__(attrs)

        for attr in self.conda_attrs:
            setattr(self.metadata, attr, given_attrs.get(attr, self.conda_attrs[attr]))

class bdist_conda(install):
    description = "create a conda package"

    def initialize_options(self):
        super(bdist_conda, self).initialize_options()
        self.buildnum = None
        self.binstar_upload = False

    def finalize_options(self):
        opt_dict = self.distribution.get_option_dict('install')
        if self.prefix:
            raise DistutilsOptionError("--prefix is not allowed")
        opt_dict['prefix'] = ("bdist_conda", config.build_prefix)
        super(bdist_conda, self).finalize_options()

    def run(self):
        # Make sure the metadata has the conda attributes, even if the
        # distclass isn't CondaDistribution. We primarily do this to simplify
        # the code below.

        metadata = self.distribution.metadata

        for attr in CondaDistribution.conda_attrs:
            if not hasattr(metadata, attr):
                setattr(metadata, attr,
                    CondaDistribution.conda_attrs[attr])

        # The command line takes precedence
        if self.buildnum is not None:
            metadata.conda_buildnum = self.buildnum

        with Locked(config.croot):
            d = defaultdict(dict)
            # Needs to be lowercase
            d['package']['name'] = metadata.name
            d['package']['version'] = metadata.version
            d['build']['number'] = metadata.conda_buildnum

            # MetaData does the auto stuff if the build string is None
            d['build']['string'] = metadata.conda_buildstr

            d['build']['binary_relocation'] = metadata.conda_binary_relocation
            d['build']['preserve_egg_dir'] = metadata.conda_preserve_egg_dir
            d['build']['features'] = metadata.conda_features
            d['build']['track_features'] = metadata.conda_track_features

            # XXX: I'm not really sure if it is correct to combine requires
            # and install_requires
            d['requirements']['run'] = d['requirements']['build'] = \
                [spec_from_line(i) for i in
                    (metadata.requires or []) +
                    (getattr(self.distribution, 'install_requires', []) or
                        [])] + ['python']
            if hasattr(self.distribution, 'tests_require'):
                # A lot of packages use extras_require['test'], but
                # tests_require is the one that is officially supported by
                # setuptools.
                d['test']['requires'] = [spec_from_line(i) for i in
                    self.distribution.tests_require or []]

            d['about']['home'] = metadata.url
            # Don't worry about classifiers. This isn't skeleton pypi. We
            # don't need to make this work with random stuff in the wild. If
            # someone writes their setup.py wrong and this doesn't work, it's
            # their fault.
            d['about']['license'] = metadata.license
            d['about']['summary'] = self.distribution.description

            # This is similar logic from conda skeleton pypi
            entry_points = getattr(self.distribution, 'entry_points', [])
            if entry_points:
                if isinstance(entry_points, string_types):
                    # makes sure it is left-shifted
                    newstr = "\n".join(x.strip() for x in
                        entry_points.split('\n'))
                    c = configparser.ConfigParser()
                    entry_points = {}
                    try:
                        c.readfp(StringIO(newstr))
                    except Exception as err:
                        # This seems to be the best error here
                        raise DistutilsGetoptError("ERROR: entry-points not understood: " + str(err) + "\nThe string was" + newstr)
                    else:
                        for section in config.sections():
                            if section in ['console_scripts', 'gui_scripts']:
                                value = ['%s=%s' % (option, config.get(section, option))
                                         for option in config.options(section)]
                                entry_points[section] = value
                            else:
                                # Make sure setuptools is added as a dependency below
                                entry_points[section] = None

                if not isinstance(entry_points, dict):
                    raise DistutilsGetoptError("ERROR: Could not add entry points. They were:\n" + entry_points)
                else:
                    cs = entry_points.get('console_scripts', [])
                    gs = entry_points.get('gui_scripts', [])
                    # We have *other* kinds of entry-points so we need
                    # setuptools at run-time
                    if not cs and not gs and len(entry_points) > 1:
                        d['requirements']['run'].append('setuptools')
                        d['requirements']['build'].append('setuptools')
                    entry_list = cs + gs
                    if gs and conda.config.platform == 'osx':
                        d['build']['osx_is_app'] = True
                    if len(cs + gs) != 0:
                        d['build']['entry_points'] = entry_list
                        if metadata.conda_command_tests == True:
                            d['test']['commands'] = pypi.make_entry_tests(entry_list)

            if 'setuptools' in d['requirements']['run']:
                d['build']['preserve_egg_dir'] = True

            if metadata.conda_import_tests:
                if metadata.conda_import_tests == True:
                    d['test']['imports'] = [metadata.name]
                    if self.distribution.packages:
                        d['test']['imports'].extend(self.distribution.packages)
                else:
                    d['test']['imports'] = metadata.conda_import_tests

            if (metadata.conda_command_tests and not
                isinstance(metadata.conda_command_tests,
                bool)):
                d['test']['commands'] = metadata.conda_command_tests

            d = dict(d)
            m = MetaData.fromdict(d)
            # Shouldn't fail, but do you really trust the code above?
            m.check_fields()
            build.build(m, post=False)
            # Do the install
            super(bdist_conda, self).run()
            build.build(m, post=True)
            build.test(m)
            if self.binstar_upload:
                class args:
                    binstar_upload = self.binstar_upload
                handle_binstar_upload(build.bldpkg_path(m), args)
            else:
                no_upload_message = """\
# If you want to upload this package to binstar.org later, type:
#
# $ binstar upload %s
""" % build.bldpkg_path(m)
                print(no_upload_message)

# Distutils looks for user_options on the class (not instance).  It also
# requires that it is an instance of list. So we do this here because we want
# to keep the options from the superclass (and because I don't feel like
# making a metaclass just to make this work).

bdist_conda.user_options.extend([
    ('buildnum=', None, '''The build number of
    the conda package. Defaults to 0, or the conda_buildnum specified in the
    setup() function. The command line flag overrides the option to
    setup().'''),
    ('binstar-upload', None, """Upload the finished package to binstar"""),
    ])

bdist_conda.boolean_options.extend(['binstar-upload'])
<|endoftext|>


<commit_before>from buffer import Buffer
from crypto import encrypt, decrypt

import socket
import struct

class Connection:
    MAGIC = 'VT01'

    def __init__(self, host, port):
        self.key = None
        self.old = ''
        self.socket = socket.socket()
        self.socket.settimeout(5)
        self.socket.connect((host, port))
        self.socket.settimeout(30)
        self.length = 0

    def send(self, data):
        if self.key:
            data = encrypt(data, self.key)

        length = struct.pack('<I', len(data))
        self.socket.send(length + self.MAGIC + data)

    def recv(self):
        data = self.socket.recv(4096)
        if data == '':
            # TODO: something like Connection.Disconnected?
            raise socket.error('disconnected from remote host')
        return self.parse(data)

    def pump(self):
        while True:
            for packet in self.recv():
                if len(packet):
                    yield Buffer(packet)

    def parse(self, data):
        if self.old:
            data = self.old + data
            self.old = ''
        else:
            self.length, = struct.unpack('<I', data[:4])
            data = data[8:]

        if len(data) < self.length:
            self.old = data
            yield ''
        else:
            packet, data = data[:self.length], data[self.length:]
            if self.key:
                packet = decrypt(packet, self.key)
            if len(data):
                self.old = data
            yield packet
<commit_msg>fix connection unreliability<commit_after>from buffer import Buffer
from crypto import encrypt, decrypt

import socket
import struct

class Connection:
    MAGIC = 'VT01'

    def __init__(self, host, port):
        self.key = None
        self.buf = ''
        self.socket = socket.socket()
        self.socket.settimeout(5)
        self.socket.connect((host, port))
        self.socket.settimeout(30)
        self.length = None

    def send(self, data):
        if self.key:
            data = encrypt(data, self.key)
        length = struct.pack('<I', len(data))
        self.socket.send(length + self.MAGIC + data)

    def recv(self):
        data = self.socket.recv(4096)
        if data == '':
            # TODO: something like Connection.Disconnected?
            raise socket.error('disconnected from remote host')
        return self.parse(data)

    def pump(self):
        while True:
            for packet in self.recv():
                yield Buffer(packet)

    def parse(self, data):
        self.buf += data
        if self.length is None:
            if len(self.buf) < 8:
                return
            self.length, = struct.unpack('<I', self.buf[:4])
            if self.buf[4:8] != self.MAGIC:
                raise socket.error('invalid magic')
            self.buf = self.buf[8:]

        if len(self.buf) < self.length:
            return

        packet, self.buf = self.buf[:self.length], self.buf[self.length:]
        self.length = None
        if self.key:
            packet = decrypt(packet, self.key)
        yield packet
<|endoftext|>


<commit_before>from Milestones import *

def mapData(data):
	"""
	Segments the path into mini-paths of varying colors
	(color dependent on radiation levels)

	Parameters:
	data 	(List): A list of parsed log entries
	"""

	# Handle datasets of a single entry
	if len(data) == 1:
		entry = data[0]
		radColor = calcRadColor(entry)
		makeMarker(getPoint(entry), radColor)

	while len(data) > 1:
		# Make a new path
		path = []

		# Calculate the radiation color of the path
		radOne = calcRadColor(data[0])
		radTwo = calcRadColor(data[1])

		radColor = avgRadColor(radOne, radTwo)

		# Extract the lat and long and put them on the path
		path.append(data[0])
		del data[0]
		path.append(data[0])

		# Add additional points to the path, if applicable
		while len(data) > 1:
			path.append(data[0])

			if calcRadColor(data[1]) == radColor:
				del data[0]
			else:
				break

		makePath(path, radColor)

def getPoint(entry):
	"""
	Extracts the latitude and longitude from a data entry

	Parameters:
	entry 	(List): A single parsed log entry
	"""
	return [entry[4], entry[5]]

def avgRadColor(radOne, radTwo):
	"""
	Averages two radiation colors.
	Decodes from an 8 digit hexademical ABGR string

	Parameters:
	radOne 	(String): A 32 bit ABGR hex string
	radTwo 	(String): A 32 bit ABGR hex string

	Returns an 8 digit hexadecimal ABGR string
	"""
	alpha = (int(radOne[:2], 16) + int(radTwo[:2], 16)) // 2
	alpha = hex(alpha)[2:].zfill(2)

	blue = (int(radOne[2:4], 16) + int(radTwo[2:4], 16)) // 2
	blue = hex(red)[2:].zfill(2)

	green = (int(radOne[4:6], 16) + int(radTwo[4:6], 16)) // 2
	green = hex(green)[2:].zfill(2)

	red = (int(radOne[6:8], 16) + int(radTwo[6:8], 16)) // 2
	red = hex(red)[2:].zfill(2)

	return alpha + blue + green + red

def calcRadColor(entry):
	"""
	Calculates the color for a given entry
	Uses a 32 bit ABGR color scheme in accordance with KML

	Parameters:
	entry 	(List): A single parsed log entry

	Returns an 8 digit hexadecimal ABGR string
	"""
	alpha 	= calcAlpha(int(entry[1]))
	red 	= calcRed(int(entry[1]))
	green	= calcGreen(int(entry[1]))
	blue	= calcBlue(int(entry[1]))
	return alpha + blue + green + red

"""
See documentation for details about color calculations
"""

def calcRed(radlvl):
	"""
	Calculates the level of Red

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	if radlvl <= trivialCPM:
		red = 0
	elif radlvl <= notableCPM:
		red = (radlvl - trivialCPM) * 255 // (notableCPM - trivialCPM)
	else:
		red = 255

	return hex(int(red))[2:].zfill(2)

def calcGreen(radlvl):
	"""
	Calculates the level of Green.

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	if radlvl <= notableCPM:
		green = 255
	elif radlvl <= mediumCPM:
		green = 255 - (radlvl - notableCPM) * 128 // (mediumCPM - notableCPM)
	elif radlvl <= highCPM:
		green = 128 - (radlvl - mediumCPM) * 128 // (highCPM - mediumCPM)
	else:
		green = 0

	return hex(int(green))[2:].zfill(2)

def calcBlue(radlvl):
	"""
	Calculates the level of Blue

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	# No blue in Green, Yellow, or Red
	blue = 0
	return hex(blue)[2:].zfill(2)

def calcAlpha(radlvl):
	"""
	Calculates the level of Alpha

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	# Fully opaque
	alpha = 255
	return hex(alpha)[2:].zfill(2)<commit_msg>Mappyer.py syntax error fixed<commit_after>from Milestones import *

def mapData(data):
	"""
	Segments the path into mini-paths of varying colors
	(color dependent on radiation levels)

	Parameters:
	data 	(List): A list of parsed log entries
	"""

	# Handle datasets of a single entry
	if len(data) == 1:
		entry = data[0]
		radColor = calcRadColor(entry)
		makeMarker(getPoint(entry), radColor)

	while len(data) > 1:
		# Make a new path
		path = []

		# Calculate the radiation color of the path
		radOne = calcRadColor(data[0])
		radTwo = calcRadColor(data[1])

		radColor = avgRadColor(radOne, radTwo)

		# Extract the lat and long and put them on the path
		path.append(data[0])
		del data[0]
		path.append(data[0])

		# Add additional points to the path, if applicable
		while len(data) > 1:
			path.append(data[0])

			if calcRadColor(data[1]) == radColor:
				del data[0]
			else:
				break

		makePath(path, radColor)

def getPoint(entry):
	"""
	Extracts the latitude and longitude from a data entry

	Parameters:
	entry 	(List): A single parsed log entry
	"""
	return [entry[4], entry[5]]

def avgRadColor(radOne, radTwo):
	"""
	Averages two radiation colors.
	Decodes from an 8 digit hexademical ABGR string

	Parameters:
	radOne 	(String): A 32 bit ABGR hex string
	radTwo 	(String): A 32 bit ABGR hex string

	Returns an 8 digit hexadecimal ABGR string
	"""
	alpha = (int(radOne[:2], 16) + int(radTwo[:2], 16)) // 2
	alpha = hex(alpha)[2:].zfill(2)

	blue = (int(radOne[2:4], 16) + int(radTwo[2:4], 16)) // 2
	blue = hex(blue)[2:].zfill(2)

	green = (int(radOne[4:6], 16) + int(radTwo[4:6], 16)) // 2
	green = hex(green)[2:].zfill(2)

	red = (int(radOne[6:8], 16) + int(radTwo[6:8], 16)) // 2
	red = hex(red)[2:].zfill(2)

	return alpha + blue + green + red

def calcRadColor(entry):
	"""
	Calculates the color for a given entry
	Uses a 32 bit ABGR color scheme in accordance with KML

	Parameters:
	entry 	(List): A single parsed log entry

	Returns an 8 digit hexadecimal ABGR string
	"""
	alpha 	= calcAlpha(int(entry[1]))
	red 	= calcRed(int(entry[1]))
	green	= calcGreen(int(entry[1]))
	blue	= calcBlue(int(entry[1]))
	return alpha + blue + green + red

"""
See documentation for details about color calculations
"""

def calcRed(radlvl):
	"""
	Calculates the level of Red

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	if radlvl <= trivialCPM:
		red = 0
	elif radlvl <= notableCPM:
		red = (radlvl - trivialCPM) * 255 // (notableCPM - trivialCPM)
	else:
		red = 255

	return hex(int(red))[2:].zfill(2)

def calcGreen(radlvl):
	"""
	Calculates the level of Green.

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	if radlvl <= notableCPM:
		green = 255
	elif radlvl <= mediumCPM:
		green = 255 - (radlvl - notableCPM) * 128 // (mediumCPM - notableCPM)
	elif radlvl <= highCPM:
		green = 128 - (radlvl - mediumCPM) * 128 // (highCPM - mediumCPM)
	else:
		green = 0

	return hex(int(green))[2:].zfill(2)

def calcBlue(radlvl):
	"""
	Calculates the level of Blue

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	# No blue in Green, Yellow, or Red
	blue = 0
	return hex(blue)[2:].zfill(2)

def calcAlpha(radlvl):
	"""
	Calculates the level of Alpha

	Parameters:
	radlvl 	(int): The radiation level in CPM

	Returns a 2 digit hexadecimal string
	"""
	# Fully opaque
	alpha = 255
	return hex(alpha)[2:].zfill(2)<|endoftext|>


<commit_before># Copyright 2014 Google Inc. All Rights Reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Measurements for OpenHTF.

Measurements in OpenHTF are used to represent values collected during a Test.
They can be numeric or string values, and can be configured such that the
OpenHTF framework will automatically check them against Pass/Fail criteria.
Measurements should not be used for large binary blobs, which are instead best
stored as Attachments (see attachments.py).

Measurements are described by the measurements.Measurement class.  Essentially,
the Measurement class is used by test authors to declare measurements by name,
and to optionally provide unit, type, and validation information.  Measurements
are attached to Test Phases using the @measurements.measures() decorator.

When measurements are output by the OpenHTF framework, the Measurement objects
are serialized into the 'measurements' field on the PhaseRecord, and the values
themselves are similarly output in the 'measured_values' field on the
PhaseRecord.  See test_record.py for more information.

Validation of undimensioned measurements happens when they are set, so that
users of the HTTP API can see PASS/FAIL outcome on those measurements
immediately after they are set.  Multidimensional measurements, however,
don't usually make sense to validate until all data is available, so they
instead enter a PARTIALLY_SET outcome state until the end of the test phase,
at which point they are validated and become with PASS or FAIL.  Note that
validators are only called at the end of the phase if at least one value was
set in the multidimensional measurement, otherwise it remains UNSET.

See examples/validators.py for some examples on how to define and use custom
measurement validators.

Examples:

  @measurements.measures(
      measurements.Measurement(
          'number_widgets').InRange(5, 10).Doc(
          '''This phase parameter tracks the number of widgets.'''))
  @measurements.measures(
      *(measurements.Measurement('level_%s' % lvl)
        for lvl in ('none', 'some', 'all')))
  def WidgetTestPhase(test):
    test.measurements.number_widgets = 5
    test.measurements.level_none = 10

"""

import collections
import itertools
import logging

from enum import Enum

import mutablerecords

import openhtf
from openhtf.util import validators

_LOG = logging.getLogger(__name__)

class InvalidDimensionsError(Exception):
  """Raised when there is a problem with measurement dimensions."""

class InvalidMeasurementType(Exception):
  """Raised when an unexpected measurement type is given."""

class MeasurementNotSetError(Exception):
  """Raised when a measurement is accessed that hasn't been set."""

class NotAMeasurementError(Exception):
  """Raised if an invalid measurement name is accessed."""

class DuplicateNameError(Exception):
  """An exception which occurs when a measurement name collision occurs."""

# Only multidimensional measurements can be 'PARTIALLY_SET', and can never be in
# that state after their respective phase has completed (they must transition to
# either PASS or FAIL at that point).
Outcome = Enum('Outcome', ['PASS', 'FAIL', 'UNSET', 'PARTIALLY_SET'])

class Measurement(  # pylint: disable=no-init
    mutablerecords.Record(
        'Measurement', ['name'],
        {'units': None, 'dimensions': None, 'docstring': None,
         'validators': list, 'outcome': Outcome.UNSET})):
  """Record encapsulating descriptive data for a measurement.

  This record includes an _asdict() method so it can be easily output.  Output
  is as you would expect, a dict mapping non-None fields to their values
  (validators are stringified with str()).

  Attributes:
    name: Name of the measurement.
    docstring: Optional string describing this measurement.
    units: UOM code of the units for the measurement being taken.
    dimensions: Tuple of UOM codes for units of dimensions.
    validators: List of callable validator objects to perform pass/fail checks.
    outcome: One of the Outcome() enumeration values, starting at UNSET.
  """

  def Doc(self, docstring):
    """Set this Measurement's docstring, returns self for chaining."""
    self.docstring = docstring
    return self

  def WithUnits(self, units):
    """Declare the units for this Measurement, returns self for chaining."""
    self.units = units
    return self

  def WithDimensions(self, *dimensions):
    """Declare dimensions for this Measurement, returns self for chaining."""
    self.dimensions = dimensions
    return self

  def WithValidator(self, validator):
    """Add a validator callback to this Measurement, chainable."""
    if not callable(validator):
      raise ValueError('Validator must be callable', validator)
    self.validators.append(validator)
    return self

  def WithArgs(self, **kwargs):
    """Creates a new Measurement, see openhtf.PhaseInfo.WithArgs."""
    new_meas = mutablerecords.CopyRecord(self)
    if '{' in new_meas.name:
      formatter = lambda x: x.format(**kwargs) if x else x
    else:
      # str % {'a': 1} is harmless if str doesn't use any interpolation.
      # .format is as well, but % is more likely to be used in other contexts.
      formatter = lambda x: x % kwargs if x else x
    new_meas.name = formatter(self.name)
    new_meas.docstring = formatter(self.docstring)
    return new_meas

  def __getattr__(self, attr):  # pylint: disable=invalid-name
    """Support our default set of validators as direct attributes."""
    # Don't provide a back door to validators.py private stuff accidentally.
    if attr.startswith('_') or not hasattr(validators, attr):
      raise AttributeError("'%s' object has no attribute '%s'" % (
          type(self).__name__, attr))

    # Create a wrapper to invoke the attribute from within validators.
    def _WithValidator(*args, **kwargs):  # pylint: disable=invalid-name
      return self.WithValidator(getattr(validators, attr)(*args, **kwargs))
    return _WithValidator

  def Validate(self, value):
    """Validate this measurement and update its 'outcome' field."""
    # PASS if all our validators return True, otherwise FAIL.
    if all(v(value) for v in self.validators):
      self.outcome = Outcome.PASS
    else:
      self.outcome = Outcome.FAIL
    return self

  def _asdict(self):
    """Convert this measurement to a dict of basic types."""
    retval = {
        'name': self.name,
        'outcome': self.outcome,
    }
    if len(self.validators):
      retval['validators'] = [str(v) for v in self.validators]
    for attr in ('units', 'dimensions', 'docstring'):
      if getattr(self, attr) is not None:
        retval[attr] = getattr(self, attr)
    return retval

  def MakeUnsetValue(self):
    """Create an unset MeasuredValue for this measurement."""
    if self.dimensions:
      return DimensionedMeasuredValue(self.name, len(self.dimensions), self)
    else:
      return MeasuredValue(self.name, self)

class MeasuredValue(object):
  """Class encapsulating actual values measured.

  Note that this is really just a value wrapper with some sanity checks.  See
  Declaration for the class that handles the descriptive aspect of the
  measurement.  This class is the type that Collection actually stores in
  its _values attribute.

  This class stores values for un-dimensioned (single-value) measurements, for
  dimensioned values, see the DimensionedMeasuredValue.  The interfaces are very
  similar, but differ slightly; the important part is the GetValue() interface
  on both of them.
  """

  def __init__(self, name, measurement):
    self.name = name
    self.measurement = measurement
    self.stored_value = None
    # Track this so we can differentiate between unset and set-to-None.
    self.value_set = False

  @property
  def value(self):
    if not self.value_set:
      raise MeasurementNotSetError('Measurement not yet set', self.name)
    return self.stored_value

  @value.setter
  def value(self, value):
    """Set the value for this measurement, with some sanity checks."""
    if self.value_set:
      # While we want to *allow* re-setting previously set measurements, we'd
      # rather promote the use of multidimensional measurements instead of
      # discarding data, so we make this somewhat chatty.
      _LOG.warning(
          'Overriding previous measurement %s value of %s with %s, the old '
          'value will be lost.  Use a dimensioned measurement if you need to '
          'save multiple values.', self.name, self.stored_value, value)
    self.stored_value = value
    self.value_set = True
    self.measurement.Validate(self.value)

class DimensionedMeasuredValue(object):
  """Class encapsulating actual values measured.

  See the MeasuredValue class docstring for more info.  This class provides a
  dict-like interface for indexing into dimensioned measurements.
  """

  def __init__(self, name, num_dimensions, measurement):
    assert num_dimensions > 0, 'Must have 1 or more dimensions'
    self.name = name
    self.num_dimensions = num_dimensions
    self.measurement = measurement
    self.value_dict = collections.OrderedDict()

  def __iter__(self):  # pylint: disable=invalid-name
    """Iterate over items, allows easy conversion to a dict."""
    return self.value_dict.iteritems()

  def __setitem__(self, coordinates, value):  # pylint: disable=invalid-name
    coordinates_len = len(coordinates) if hasattr(coordinates, '__len__') else 1
    if coordinates_len != self.num_dimensions:
      raise InvalidDimensionsError(
          'Expected %s-dimensional coordinates, got %s' % (self.num_dimensions,
                                                           coordinates_len))

    # Wrap single dimensions in a tuple so we can assume value_dict keys are
    # always tuples later.
    if self.num_dimensions == 1:
      coordinates = (coordinates,)

    if coordinates in self.value_dict:
      _LOG.warning(
          'Overriding previous measurement %s[%s] value of %s with %s',
          self.name, coordinates, self.value_dict[coordinates], value)
    self.value_dict[coordinates] = value
    self.measurement.outcome = Outcome.PARTIALLY_SET

  def __getitem__(self, coordinates):  # pylint: disable=invalid-name
    # Let the AttributeError propagate as that's fairly intuitive to handle.
    return self.value_dict[coordinates]

  @property
  def value(self):
    """The values stored in this record.

    Returns:
      A list of tuples; the last element of each tuple will be the measured
    value, the other elements will be the assocated coordinates.  The tuples
    are output in the order in which they were set.
    """
    return [tuple(dimensions) + (value,) for dimensions, value in
            self.value_dict.iteritems()]

class Collection(mutablerecords.Record('Collection', ['_measurements'],
                                       {'_values': dict})):
  """Encapsulates a collection of measurements.

  This collection can have measurement values retrieved and set via getters and
  setters that provide attribute and dict-like interfaces.

  A Collection is created with a list of Measurement objects (defined above).
  Measurements can't be added after initialization, only accessed and set.

  MeasuredValue values can be set as attributes (see below).  They can also be
  read as attributes, but you get a DimensioendMeasuredValue object back if the
  measurement accessed is dimensioned (this is how setting of dimensioned
  measurements works, and so is unavoidable).

  Iterating over a Collection results in (key, value) tuples of only set
  measurements and their values.  As such, a Collection can be converted to
  a dict if you want to see all of a dimensioned measurement's values.
  Alternatively, DimensionedMeasuredValue objects can also be converted directly
  to dicts with dict(), as they also support an __iter__() interface.

  This class is intended for use only internally within the OpenHTF framework.

  Example:
    from openhtf.util import measurements
    from openhtf.util.units import UOM

    self.measurements = measurements.Collection([
        measurements.Measurement('widget_height'),
        measurements.Measurement('widget_freq_response').WithDimensions(
            UOM['HERTZ'])])
    self.measurements.widget_height = 3
    print self.measurements.widget_height            # 3
    self.measurements.widget_freq_response[5] = 10
    print self.measurements.widget_freq_response[5]  # 10
    self.measurements.widget_freq_response[6] = 11
    print dict(self.measurements.widget_freq_response)
    # {5: 10, 6: 11}

    # Not recommended, but you can also do this.  This is intended only for
    # framework internal use when generating the output test record.
    print dict(self.measurements)['widget_freq_response']
    # [(5, 10), (6, 11)]
  """

  def _AssertValidKey(self, name):
    """Raises if name is not a valid measurement."""
    if name not in self._measurements:
      raise NotAMeasurementError('Not a measurement', name, self._measurements)

  def __iter__(self):  # pylint: disable=invalid-name
    def _GetMeasurementValue(item):  # pylint: disable=invalid-name
      """Extract a single MeasurementValue's value."""
      return item[0], item[1].value
    return itertools.imap(_GetMeasurementValue, self._values.iteritems())

  def __setattr__(self, name, value):  # pylint: disable=invalid-name
    self[name] = value

  def __getattr__(self, name):  # pylint: disable=invalid-name
    return self[name]

  def __setitem__(self, name, value):  # pylint: disable=invalid-name
    self._AssertValidKey(name)
    if self._measurements[name].dimensions:
      raise InvalidDimensionsError(
          'Cannot set dimensioned measurement without indices')
    self._values.setdefault(
        name, self._measurements[name].MakeUnsetValue()).value = value

  def __getitem__(self, name):  # pylint: disable=invalid-name
    self._AssertValidKey(name)

    # __getitem__ is used to set dimensioned values via __setitem__ on the
    # DimensionedMeasuredValue object, so we can't do any checking here.
    if self._measurements[name].dimensions:
      return self._values.setdefault(
          name, self._measurements[name].MakeUnsetValue())

    # For regular measurements, however, we can check that it's been set.
    if name not in self._values:
      raise MeasurementNotSetError('Measurement not yet set', name)
    return self._values[name].value

def measures(*measurements, **kwargs):
  """Decorator-maker used to declare measurements for phases.

  See the measurements module docstring for examples of usage.

  Args:
    measurements: Measurement objects to declare, or a string name from which
        to create a Measurement.
    kwargs: Keyword arguments to pass to Measurement constructor if we're
        constructing one.  Note that if kwargs are provided, the length
        of measurements must be 1, and that value must be a string containing
        the measurement name.  For valid kwargs, see the definition of the
        Measurement class.

  Returns:
    A decorator that declares the measurement(s) for the decorated phase.
  """
  def _maybe_make(meas):
    """Turn strings into Measurement objects if necessary."""
    if isinstance(meas, Measurement):
      return meas
    elif isinstance(meas, basestring):
      return Measurement(meas, **kwargs)
    raise InvalidMeasurementType('Expected Measurement or string', meas)

  # In case we're declaring a measurement inline, we can only declare one.
  if kwargs and len(measurements) != 1:
    raise InvalidMeasurementType(
        'If @measures kwargs are provided, a single measurement name must be '
        'provided as a positional arg first.')

  # Unlikely, but let's make sure we don't allow overriding initial outcome.
  if 'outcome' in kwargs:
    raise ValueError('Cannot specify outcome in measurement declaration!')

  measurements = [_maybe_make(meas) for meas in measurements]

  # 'measurements' is guaranteed to be a list of Measurement objects here.
  def decorate(wrapped_phase):
    """Phase decorator to be returned."""
    phase = openhtf.PhaseInfo.WrapOrCopy(wrapped_phase)
    duplicate_names = (set(m.name for m in measurements) &
                       set(m.name for m in phase.measurements))
    if duplicate_names:
      raise DuplicateNameError('Measurement names duplicated', duplicate_names)

    phase.measurements.extend(measurements)
    return phase
  return decorate
<commit_msg>code review updates<commit_after># Copyright 2014 Google Inc. All Rights Reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Measurements for OpenHTF.

Measurements in OpenHTF are used to represent values collected during a Test.
They can be numeric or string values, and can be configured such that the
OpenHTF framework will automatically check them against Pass/Fail criteria.
Measurements should not be used for large binary blobs, which are instead best
stored as Attachments (see attachments.py).

Measurements are described by the measurements.Measurement class.  Essentially,
the Measurement class is used by test authors to declare measurements by name,
and to optionally provide unit, type, and validation information.  Measurements
are attached to Test Phases using the @measurements.measures() decorator.

When measurements are output by the OpenHTF framework, the Measurement objects
are serialized into the 'measurements' field on the PhaseRecord, and the values
themselves are similarly output in the 'measured_values' field on the
PhaseRecord.  See test_record.py for more information.

Validation of undimensioned measurements happens when they are set, so that
users of the HTTP API can see PASS/FAIL outcome on those measurements
immediately after they are set.  Multidimensional measurements, however,
don't usually make sense to validate until all data is available, so they
instead enter a PARTIALLY_SET outcome state until the end of the test phase,
at which point they are validated and become with PASS or FAIL.  Note that
validators of dimensioned measurements are only called at the end of the phase
if at least one value was set in the multidimensional measurement, otherwise it
remains UNSET, so that outcome fields for all measurements may be PASS, FAIL,
or UNSET.

# TODO(madsci): Make validators.py example.
See examples/validators.py for some examples on how to define and use custom
measurement validators.

Examples:

  @measurements.measures(
      measurements.Measurement(
          'number_widgets').InRange(5, 10).Doc(
          '''This phase parameter tracks the number of widgets.'''))
  @measurements.measures(
      *(measurements.Measurement('level_%s' % lvl)
        for lvl in ('none', 'some', 'all')))
  def WidgetTestPhase(test):
    test.measurements.number_widgets = 5
    test.measurements.level_none = 10

"""

import collections
import itertools
import logging

from enum import Enum

import mutablerecords

import openhtf
from openhtf.util import validators

_LOG = logging.getLogger(__name__)

class InvalidDimensionsError(Exception):
  """Raised when there is a problem with measurement dimensions."""

class InvalidMeasurementType(Exception):
  """Raised when an unexpected measurement type is given."""

class MeasurementNotSetError(Exception):
  """Raised when a measurement is accessed that hasn't been set."""

class NotAMeasurementError(Exception):
  """Raised if an invalid measurement name is accessed."""

class DuplicateNameError(Exception):
  """An exception which occurs when a measurement name collision occurs."""

# Only multidimensional measurements can be 'PARTIALLY_SET', and can never be in
# that state after their respective phase has completed (they must transition to
# either PASS or FAIL at that point).
Outcome = Enum('Outcome', ['PASS', 'FAIL', 'UNSET', 'PARTIALLY_SET'])

class Measurement(  # pylint: disable=no-init
    mutablerecords.Record(
        'Measurement', ['name'],
        {'units': None, 'dimensions': None, 'docstring': None,
         'validators': list, 'outcome': Outcome.UNSET})):
  """Record encapsulating descriptive data for a measurement.

  This record includes an _asdict() method so it can be easily output.  Output
  is as you would expect, a dict mapping non-None fields to their values
  (validators are stringified with str()).

  Attributes:
    name: Name of the measurement.
    docstring: Optional string describing this measurement.
    units: UOM code of the units for the measurement being taken.
    dimensions: Tuple of UOM codes for units of dimensions.
    validators: List of callable validator objects to perform pass/fail checks.
    outcome: One of the Outcome() enumeration values, starting at UNSET.
  """

  def Doc(self, docstring):
    """Set this Measurement's docstring, returns self for chaining."""
    self.docstring = docstring
    return self

  def WithUnits(self, units):
    """Declare the units for this Measurement, returns self for chaining."""
    self.units = units
    return self

  def WithDimensions(self, *dimensions):
    """Declare dimensions for this Measurement, returns self for chaining."""
    self.dimensions = dimensions
    return self

  def WithValidator(self, validator):
    """Add a validator callback to this Measurement, chainable."""
    if not callable(validator):
      raise ValueError('Validator must be callable', validator)
    self.validators.append(validator)
    return self

  def WithArgs(self, **kwargs):
    """Creates a new Measurement, see openhtf.PhaseInfo.WithArgs."""
    new_meas = mutablerecords.CopyRecord(self)
    if '{' in new_meas.name:
      formatter = lambda x: x.format(**kwargs) if x else x
    else:
      # str % {'a': 1} is harmless if str doesn't use any interpolation.
      # .format is as well, but % is more likely to be used in other contexts.
      formatter = lambda x: x % kwargs if x else x
    new_meas.name = formatter(self.name)
    new_meas.docstring = formatter(self.docstring)
    return new_meas

  def __getattr__(self, attr):  # pylint: disable=invalid-name
    """Support our default set of validators as direct attributes."""
    # Don't provide a back door to validators.py private stuff accidentally.
    if attr.startswith('_') or not hasattr(validators, attr):
      raise AttributeError("'%s' object has no attribute '%s'" % (
          type(self).__name__, attr))

    # Create a wrapper to invoke the attribute from within validators.
    def _WithValidator(*args, **kwargs):  # pylint: disable=invalid-name
      return self.WithValidator(getattr(validators, attr)(*args, **kwargs))
    return _WithValidator

  def Validate(self, value):
    """Validate this measurement and update its 'outcome' field."""
    # PASS if all our validators return True, otherwise FAIL.
    if all(v(value) for v in self.validators):
      self.outcome = Outcome.PASS
    else:
      self.outcome = Outcome.FAIL
    return self

  def _asdict(self):
    """Convert this measurement to a dict of basic types."""
    retval = {
        'name': self.name,
        'outcome': self.outcome,
    }
    if len(self.validators):
      retval['validators'] = [str(v) for v in self.validators]
    for attr in ('units', 'dimensions', 'docstring'):
      if getattr(self, attr) is not None:
        retval[attr] = getattr(self, attr)
    return retval

  def MakeUnsetValue(self):
    """Create an unset MeasuredValue for this measurement."""
    if self.dimensions:
      return DimensionedMeasuredValue(self.name, len(self.dimensions), self)
    else:
      return MeasuredValue(self.name, self)

class MeasuredValue(object):
  """Class encapsulating actual values measured.

  Note that this is really just a value wrapper with some sanity checks.  See
  Declaration for the class that handles the descriptive aspect of the
  measurement.  This class is the type that Collection actually stores in
  its _values attribute.

  This class stores values for un-dimensioned (single-value) measurements, for
  dimensioned values, see the DimensionedMeasuredValue.  The interfaces are very
  similar, but differ slightly; the important part is the GetValue() interface
  on both of them.
  """

  def __init__(self, name, measurement):
    self.name = name
    self.measurement = measurement
    self.stored_value = None
    # Track this so we can differentiate between unset and set-to-None.
    self.value_set = False

  @property
  def value(self):
    if not self.value_set:
      raise MeasurementNotSetError('Measurement not yet set', self.name)
    return self.stored_value

  @value.setter
  def value(self, value):
    """Set the value for this measurement, with some sanity checks."""
    if self.value_set:
      # While we want to *allow* re-setting previously set measurements, we'd
      # rather promote the use of multidimensional measurements instead of
      # discarding data, so we make this somewhat chatty.
      _LOG.warning(
          'Overriding previous measurement %s value of %s with %s, the old '
          'value will be lost.  Use a dimensioned measurement if you need to '
          'save multiple values.', self.name, self.stored_value, value)
    self.stored_value = value
    self.value_set = True
    self.measurement.Validate(self.value)

class DimensionedMeasuredValue(object):
  """Class encapsulating actual values measured.

  See the MeasuredValue class docstring for more info.  This class provides a
  dict-like interface for indexing into dimensioned measurements.
  """

  def __init__(self, name, num_dimensions, measurement):
    assert num_dimensions > 0, 'Must have 1 or more dimensions'
    self.name = name
    self.num_dimensions = num_dimensions
    self.measurement = measurement
    self.value_dict = collections.OrderedDict()

  def __iter__(self):  # pylint: disable=invalid-name
    """Iterate over items, allows easy conversion to a dict."""
    return self.value_dict.iteritems()

  def __setitem__(self, coordinates, value):  # pylint: disable=invalid-name
    coordinates_len = len(coordinates) if hasattr(coordinates, '__len__') else 1
    if coordinates_len != self.num_dimensions:
      raise InvalidDimensionsError(
          'Expected %s-dimensional coordinates, got %s' % (self.num_dimensions,
                                                           coordinates_len))

    # Wrap single dimensions in a tuple so we can assume value_dict keys are
    # always tuples later.
    if self.num_dimensions == 1:
      coordinates = (coordinates,)

    if coordinates in self.value_dict:
      _LOG.warning(
          'Overriding previous measurement %s[%s] value of %s with %s',
          self.name, coordinates, self.value_dict[coordinates], value)
    self.value_dict[coordinates] = value
    self.measurement.outcome = Outcome.PARTIALLY_SET

  def __getitem__(self, coordinates):  # pylint: disable=invalid-name
    # Wrap single dimensions in a tuple so we can assume value_dict keys are
    # always tuples later.
    if self.num_dimensions == 1:
      coordinates = (coordinates,)
    return self.value_dict[coordinates]

  @property
  def value(self):
    """The values stored in this record.

    Returns:
      A list of tuples; the last element of each tuple will be the measured
    value, the other elements will be the assocated coordinates.  The tuples
    are output in the order in which they were set.
    """
    return [dimensions + (value,) for dimensions, value in
            self.value_dict.iteritems()]

class Collection(mutablerecords.Record('Collection', ['_measurements'],
                                       {'_values': dict})):
  """Encapsulates a collection of measurements.

  This collection can have measurement values retrieved and set via getters and
  setters that provide attribute and dict-like interfaces.

  A Collection is created with a list of Measurement objects (defined above).
  Measurements can't be added after initialization, only accessed and set.

  MeasuredValue values can be set as attributes (see below).  They can also be
  read as attributes, but you get a DimensionedMeasuredValue object back if the
  measurement accessed is dimensioned (this is how setting of dimensioned
  measurements works, and so is unavoidable).

  Iterating over a Collection results in (key, value) tuples of only set
  measurements and their values.  As such, a Collection can be converted to
  a dict if you want to see all of a dimensioned measurement's values.
  Alternatively, DimensionedMeasuredValue objects can also be converted directly
  to dicts with dict(), as they also support an __iter__() interface.

  This class is intended for use only internally within the OpenHTF framework.

  Example:
    from openhtf.util import measurements
    from openhtf.util.units import UOM

    self.measurements = measurements.Collection([
        measurements.Measurement('widget_height'),
        measurements.Measurement('widget_freq_response').WithDimensions(
            UOM['HERTZ'])])
    self.measurements.widget_height = 3
    print self.measurements.widget_height            # 3
    self.measurements.widget_freq_response[5] = 10
    print self.measurements.widget_freq_response[5]  # 10
    self.measurements.widget_freq_response[6] = 11
    print dict(self.measurements.widget_freq_response)
    # {5: 10, 6: 11}

    # Not recommended, but you can also do this.  This is intended only for
    # framework internal use when generating the output test record.
    print dict(self.measurements)['widget_freq_response']
    # [(5, 10), (6, 11)]
  """

  def _AssertValidKey(self, name):
    """Raises if name is not a valid measurement."""
    if name not in self._measurements:
      raise NotAMeasurementError('Not a measurement', name, self._measurements)

  def __iter__(self):  # pylint: disable=invalid-name
    def _GetMeasurementValue(item):  # pylint: disable=invalid-name
      """Extract a single MeasurementValue's value."""
      return item[0], item[1].value
    return itertools.imap(_GetMeasurementValue, self._values.iteritems())

  def __setattr__(self, name, value):  # pylint: disable=invalid-name
    self[name] = value

  def __getattr__(self, name):  # pylint: disable=invalid-name
    return self[name]

  def __setitem__(self, name, value):  # pylint: disable=invalid-name
    self._AssertValidKey(name)
    if self._measurements[name].dimensions:
      raise InvalidDimensionsError(
          'Cannot set dimensioned measurement without indices')
    if name not in self._values:
      self._values[name] = self._measurements[name].MakeUnsetValue()
    self._values[name].value = value

  def __getitem__(self, name):  # pylint: disable=invalid-name
    self._AssertValidKey(name)

    # __getitem__ is used to set dimensioned values via __setitem__ on the
    # DimensionedMeasuredValue object, so we can't do any checking here.
    if self._measurements[name].dimensions:
      if name not in self._values:
        self._values[name] = self._measurements[name].MakeUnsetValue()
      return self._values[name]

    # For regular measurements, however, we can check that it's been set.
    if name not in self._values:
      raise MeasurementNotSetError('Measurement not yet set', name)
    return self._values[name].value

def measures(*measurements, **kwargs):
  """Decorator-maker used to declare measurements for phases.

  See the measurements module docstring for examples of usage.

  Args:
    measurements: Measurement objects to declare, or a string name from which
        to create a Measurement.
    kwargs: Keyword arguments to pass to Measurement constructor if we're
        constructing one.  Note that if kwargs are provided, the length
        of measurements must be 1, and that value must be a string containing
        the measurement name.  For valid kwargs, see the definition of the
        Measurement class.

  Returns:
    A decorator that declares the measurement(s) for the decorated phase.
  """
  def _maybe_make(meas):
    """Turn strings into Measurement objects if necessary."""
    if isinstance(meas, Measurement):
      return meas
    elif isinstance(meas, basestring):
      return Measurement(meas, **kwargs)
    raise InvalidMeasurementType('Expected Measurement or string', meas)

  # In case we're declaring a measurement inline, we can only declare one.
  if kwargs and len(measurements) != 1:
    raise InvalidMeasurementType(
        'If @measures kwargs are provided, a single measurement name must be '
        'provided as a positional arg first.')

  # Unlikely, but let's make sure we don't allow overriding initial outcome.
  if 'outcome' in kwargs:
    raise ValueError('Cannot specify outcome in measurement declaration!')

  measurements = [_maybe_make(meas) for meas in measurements]

  # 'measurements' is guaranteed to be a list of Measurement objects here.
  def decorate(wrapped_phase):
    """Phase decorator to be returned."""
    phase = openhtf.PhaseInfo.WrapOrCopy(wrapped_phase)
    duplicate_names = (set(m.name for m in measurements) &
                       set(m.name for m in phase.measurements))
    if duplicate_names:
      raise DuplicateNameError('Measurement names duplicated', duplicate_names)

    phase.measurements.extend(measurements)
    return phase
  return decorate
<|endoftext|>