<commit_before>from django.template.loader import render_to_string
from . import DTSortDirection, DTSortType

class DataTablesColumn(object):
    rowspan = 1

    def __init__(self, name, span=0, sort_type=None, sort_direction=None,
                 help_text=None, sortable=True, rotate=False,
                 expected=None, prop_name=None):
        self.html = name
        self.css_span = span
        self.sort_type = sort_type
        self.sort_direction = sort_direction if sort_direction else [DTSortDirection.ASC, DTSortDirection.DSC]
        self.help_text = help_text
        self.sortable = sortable
        self.rotate = rotate
        self.prop_name = prop_name
        if isinstance(expected, int):
            expected = "%d" % expected
        self.expected = expected

    @property
    def render_html(self):
        column_params = dict(
            title=self.html,
            sort=self.sortable,
            rotate=self.rotate,
            css="span%d" % self.css_span if self.css_span > 0 else '',
            rowspan=self.rowspan,
            help_text=self.help_text,
            expected=self.expected
        )
        return render_to_string("reports/datatables/column.html", dict(
            col=column_params
        ))

    @property
    def render_aoColumns(self):
        aoColumns = dict(asSorting=self.sort_direction)

        if self.prop_name:
            aoColumns['sName'] = self.prop_name
        if self.sort_type:
            aoColumns["sType"] = self.sort_type
        if not self.sortable:
            aoColumns["bSortable"] = self.sortable
        if self.rotate:
            aoColumns["sWidth"] = '10px'
        return aoColumns

class NumericColumn(DataTablesColumn):
    def __init__(self, *args, **kwargs):
        return super(NumericColumn, self).__init__(
            sort_type=DTSortType.NUMERIC, sortable=True, *args, **kwargs)

class DataTablesColumnGroup(object):
    css_span = 0

    def __init__(self, name, *args):
        self.columns = list()
        self.html = name
        for col in args:
            if isinstance(col, DataTablesColumn):
                self.add_column(col)

    def add_column(self, column):
        self.columns.append(column)
        self.css_span += column.css_span

    def remove_column(self, column):
        self.columns.remove(column)
        self.css_span -= column.css_span

    @property
    def render_html(self):
        template = '%(title)s	'
        css_class = ' class="span%d"' % self.css_span if self.css_span > 0 else ''
        return template % dict(title=self.html, css_class=css_class, colspan=len(self.columns)) if self.columns else ""

    @property
    def render_group_html(self):
        group = list()
        for col in self.columns:
            group.append(col.render_html)
        return "\n".join(group)

    @property
    def render_aoColumns(self):
        aoColumns = list()
        for col in self.columns:
            aoColumns.append(col.render_aoColumns)
        return aoColumns

    def __iter__(self):
        for col in self.columns:
            yield col

    def __len__(self):
        length = 0
        for _ in self:
            length += 1
        return length

    def __nonzero__(self):
        return True

class DataTablesHeader(object):
    has_group = False
    no_sort = False
    complex = True
    span = 0
    custom_sort = None

    def __init__(self, *args):
        self.header = list()
        for col in args:
            if isinstance(col, DataTablesColumnGroup):
                self.has_group = True
            if isinstance(col, DataTablesColumnGroup) or \
               isinstance(col, DataTablesColumn):
                self.add_column(col)

    def add_column(self, column):
        self.header.append(column)
        self.span += column.css_span
        self.check_auto_width()

    def remove_column(self, column):
        self.header.remove(column)
        self.span -= column.css_span
        self.check_auto_width()

    def prepend_column(self, column):
        self.header = [column] + self.header
        self.span += column.css_span
        self.check_auto_width()

    def insert_column(self, column, index):
        self.span += column.css_span
        self.header = self.header[:index] + [column] + self.header[index:]
        self.check_auto_width()

    def check_auto_width(self):
        self.auto_width = bool(0 < self.span <= 12)

    @property
    def as_export_table(self):
        head = list()
        groups = list()
        use_groups = False
        for column in self.header:
            if isinstance(column, DataTablesColumnGroup):
                use_groups = True
                groups.extend([column.html] + [" "] * (len(column.columns) - 1))
                for child_columns in column.columns:
                    head.append(child_columns.html)
            else:
                head.append(column.html)
                groups.append(" ")
        if use_groups:
            return [groups, head]
        else:
            return [head]

    @property
    def render_html(self):
        head = list()
        groups = list()
        head.append("
")
        groups.append("
")
        for column in self.header:
            if isinstance(column, DataTablesColumn):
                column.rowspan = 2 if self.has_group else 1
                if self.no_sort:
                    column.sortable = False
            elif isinstance(column, DataTablesColumnGroup):
                groups.append(column.render_group_html)
            head.append(column.render_html)
        head.append("
")
        groups.append("
")
        if len(groups) > 2:
            head.extend(groups)
        return "\n".join(head)

    @property
    def render_aoColumns(self):
        aoColumns = list()
        for column in self.header:
            if isinstance(column, DataTablesColumnGroup):
                aoColumns.extend(column.render_aoColumns)
            else:
                aoColumns.append(column.render_aoColumns)
        return aoColumns

    def __iter__(self):
        for column in self.header:
            yield column

    def __len__(self):
        length = 0
        for col in self:
            length += len(col) if isinstance(col, DataTablesColumnGroup) else 1
        return length

    def __nonzero__(self):
        return True
<commit_msg>fix encoding issue for table headers on export<commit_after>from django.template.loader import render_to_string
from . import DTSortDirection, DTSortType

class DataTablesColumn(object):
    rowspan = 1

    def __init__(self, name, span=0, sort_type=None, sort_direction=None,
                 help_text=None, sortable=True, rotate=False,
                 expected=None, prop_name=None):
        self.html = name
        self.css_span = span
        self.sort_type = sort_type
        self.sort_direction = sort_direction if sort_direction else [DTSortDirection.ASC, DTSortDirection.DSC]
        self.help_text = help_text
        self.sortable = sortable
        self.rotate = rotate
        self.prop_name = prop_name
        if isinstance(expected, int):
            expected = "%d" % expected
        self.expected = expected

    @property
    def render_html(self):
        column_params = dict(
            title=self.html,
            sort=self.sortable,
            rotate=self.rotate,
            css="span%d" % self.css_span if self.css_span > 0 else '',
            rowspan=self.rowspan,
            help_text=self.help_text,
            expected=self.expected
        )
        return render_to_string("reports/datatables/column.html", dict(
            col=column_params
        ))

    @property
    def render_aoColumns(self):
        aoColumns = dict(asSorting=self.sort_direction)

        if self.prop_name:
            aoColumns['sName'] = self.prop_name
        if self.sort_type:
            aoColumns["sType"] = self.sort_type
        if not self.sortable:
            aoColumns["bSortable"] = self.sortable
        if self.rotate:
            aoColumns["sWidth"] = '10px'
        return aoColumns

class NumericColumn(DataTablesColumn):
    def __init__(self, *args, **kwargs):
        return super(NumericColumn, self).__init__(
            sort_type=DTSortType.NUMERIC, sortable=True, *args, **kwargs)

class DataTablesColumnGroup(object):
    css_span = 0

    def __init__(self, name, *args):
        self.columns = list()
        self.html = name
        for col in args:
            if isinstance(col, DataTablesColumn):
                self.add_column(col)

    def add_column(self, column):
        self.columns.append(column)
        self.css_span += column.css_span

    def remove_column(self, column):
        self.columns.remove(column)
        self.css_span -= column.css_span

    @property
    def render_html(self):
        template = '%(title)s	'
        css_class = ' class="span%d"' % self.css_span if self.css_span > 0 else ''
        return template % dict(title=self.html, css_class=css_class, colspan=len(self.columns)) if self.columns else ""

    @property
    def render_group_html(self):
        group = list()
        for col in self.columns:
            group.append(col.render_html)
        return "\n".join(group)

    @property
    def render_aoColumns(self):
        aoColumns = list()
        for col in self.columns:
            aoColumns.append(col.render_aoColumns)
        return aoColumns

    def __iter__(self):
        for col in self.columns:
            yield col

    def __len__(self):
        length = 0
        for _ in self:
            length += 1
        return length

    def __nonzero__(self):
        return True

class DataTablesHeader(object):
    has_group = False
    no_sort = False
    complex = True
    span = 0
    custom_sort = None

    def __init__(self, *args):
        self.header = list()
        for col in args:
            if isinstance(col, DataTablesColumnGroup):
                self.has_group = True
            if isinstance(col, DataTablesColumnGroup) or \
               isinstance(col, DataTablesColumn):
                self.add_column(col)

    def add_column(self, column):
        self.header.append(column)
        self.span += column.css_span
        self.check_auto_width()

    def remove_column(self, column):
        self.header.remove(column)
        self.span -= column.css_span
        self.check_auto_width()

    def prepend_column(self, column):
        self.header = [column] + self.header
        self.span += column.css_span
        self.check_auto_width()

    def insert_column(self, column, index):
        self.span += column.css_span
        self.header = self.header[:index] + [column] + self.header[index:]
        self.check_auto_width()

    def check_auto_width(self):
        self.auto_width = bool(0 < self.span <= 12)

    @property
    def as_export_table(self):
        head = list()
        groups = list()
        use_groups = False
        for column in self.header:
            if isinstance(column, DataTablesColumnGroup):
                use_groups = True
                groups.extend([column.html] + [" "] * (len(column.columns) - 1))
                for child_columns in column.columns:
                    head.append(child_columns.html)
            else:
                head.append(column.html)
                groups.append(" ")

        head = map(lambda h: h.decode('utf-8'), head)
        if use_groups:
            return [groups, head]
        else:
            return [head]

    @property
    def render_html(self):
        head = list()
        groups = list()
        head.append("
")
        groups.append("
")
        for column in self.header:
            if isinstance(column, DataTablesColumn):
                column.rowspan = 2 if self.has_group else 1
                if self.no_sort:
                    column.sortable = False
            elif isinstance(column, DataTablesColumnGroup):
                groups.append(column.render_group_html)
            head.append(column.render_html)
        head.append("
")
        groups.append("
")
        if len(groups) > 2:
            head.extend(groups)
        return "\n".join(head)

    @property
    def render_aoColumns(self):
        aoColumns = list()
        for column in self.header:
            if isinstance(column, DataTablesColumnGroup):
                aoColumns.extend(column.render_aoColumns)
            else:
                aoColumns.append(column.render_aoColumns)
        return aoColumns

    def __iter__(self):
        for column in self.header:
            yield column

    def __len__(self):
        length = 0
        for col in self:
            length += len(col) if isinstance(col, DataTablesColumnGroup) else 1
        return length

    def __nonzero__(self):
        return True
<|endoftext|>


<commit_before># No shebang line, this module is meant to be imported
#
# Copyright 2014 Oliver Palmer
#
# 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.

from decimal import Decimal

try:
    from httplib import ACCEPTED, BAD_REQUEST
except ImportError:  # pragma: no cover
    from http.client import ACCEPTED, BAD_REQUEST

from twisted.web.server import NOT_DONE_YET
from voluptuous import Invalid, Schema, Required, Optional, Any

from pyfarm.core.enums import STRING_TYPES
from pyfarm.core.sysinfo.memory import ram_free
from pyfarm.core.logger import getLogger
from pyfarm.agent.config import config
from pyfarm.agent.http.api.base import APIResource

logger = getLogger("agent.assign")

# Values used by the schema to do type testing
# of input requests
STRINGS = Any(*STRING_TYPES)
try:
    WHOLE_NUMBERS = Any(*(int, long))
    NUMBERS = Any(*(int, long, float, Decimal))
except NameError:  # pragma: no cover
    WHOLE_NUMBERS = int
    NUMBERS = Any(*(int, float, Decimal))

def validate_environment(values):
    """
    Ensures that ``values`` is a dictionary and that it only
    contains string keys and values.
    """
    if not isinstance(values, dict):
        raise Invalid("Expected a dictionary")

    for key, value in values.items():
        if not isinstance(key, STRING_TYPES):
            raise Invalid("Key %r must be a string" % key)

        if not isinstance(value, STRING_TYPES):
            raise Invalid("Value %r for key %r must be a string" % (key, value))

class HandlePost(object):
    def __init__(self, data):
        self.data = data

class Assign(APIResource):
    isLeaf = False  # this is not really a collection of things

    # Schemas used for validating the request before
    # the target function will handle it.  These make
    # assertions about what kind of input data is required
    # or not based on the agent's internal cod.
    SCHEMAS = {
        "POST": Schema({
            Required("job"): Schema({
                Required("id"): WHOLE_NUMBERS,
                Required("by"): NUMBERS,
                Optional("batch"): WHOLE_NUMBERS,
                Optional("user"): STRINGS,
                Optional("ram"): WHOLE_NUMBERS,
                Optional("ram_warning"): WHOLE_NUMBERS,
                Optional("ram_max"): WHOLE_NUMBERS,
                Optional("cpus"): WHOLE_NUMBERS,
                Optional("data"): dict,
                Optional("environ"): validate_environment,
                Optional("title"): STRINGS}),
            Required("jobtype"): {
                Required("name"): STRINGS,
                Required("version"): WHOLE_NUMBERS},
            Required("tasks"): lambda values: map(
                Schema({
                    Required("id"): WHOLE_NUMBERS,
                    Required("frame"): NUMBERS}), values)})}

    def post(self, **kwargs):
        request = kwargs["request"]
        data = kwargs["data"]

        # First, get the resources we have *right now*.  In some cases
        # this means using the functions in pyfarm.core.sysinfo because
        # entries in `config` could be slightly out of sync with the system.
        memory_free = int(ram_free())
        cpus = config["cpus"]
        requires_ram = data["job"].get("ram")
        requires_cpus = data["job"].get("cpus")

        # Do we have enough ram?
        if requires_ram is not None and requires_ram > memory_free:
            logger.error(
                "Task %s requires %sMB of ram, this agent has %sMB free.  "
                "Rejecting Task %s.",
                data["job"]["id"], requires_ram, memory_free, data["job"]["id"])
            request.write(
                {"error": "Not enough ram",
                 "agent_ram": memory_free,
                 "requires_ram": requires_ram})
            request.setResponseCode(BAD_REQUEST)
            request.finish()

            # touch the config
            config["free_ram"] = memory_free

        # Do we have enough cpus (count wise)?
        elif requires_cpus is not None and requires_cpus > cpus:
            logger.error(
                "Task %s requires %s CPUs, this agent has %s CPUs.  "
                "Rejecting Task %s.",
                data["job"]["id"], requires_cpus, cpus, data["job"]["id"])
            request.write(
                {"error": "Not enough cpus",
                 "agent_cpus": cpus,
                 "requires_cpus": requires_cpus})
            request.setResponseCode(BAD_REQUEST)
            request.finish()

        # In all other cases we have some work to do inside of
        # deferreds so we just have to respond
        else:
            request.setResponseCode(ACCEPTED)
            request.finish()

        # TODO: Next steps as deferreds.  Some of these are done in the queue,
        #       or will be, but the information the agent has available may be
        #       more up to date and these checks are fast anyway.
        #   - ensure we have enough ram left to serve the optional requirements
        #   - check for number of cpus currently required by other jobs + this
        #     job to see if we've hit a limit
        #   - get job type (and cache if not already)
        #   - instance the job type with env, user, resource requirements, etc
        #   - execute the job type
        #   - (done) - workload handed off to job type including
        #     ram_max/ram_warning check, user -> uid conversion (or warning, on
        #     windows), etc

        return NOT_DONE_YET
<commit_msg>Placeholder job type retrieval.  Eventuall the job type itself will retrieve its own code<commit_after># No shebang line, this module is meant to be imported
#
# Copyright 2014 Oliver Palmer
#
# 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.

from decimal import Decimal

try:
    from httplib import ACCEPTED, BAD_REQUEST
except ImportError:  # pragma: no cover
    from http.client import ACCEPTED, BAD_REQUEST

from twisted.web.server import NOT_DONE_YET
from voluptuous import Invalid, Schema, Required, Optional, Any

from pyfarm.core.enums import STRING_TYPES
from pyfarm.core.sysinfo.memory import ram_free
from pyfarm.core.logger import getLogger
from pyfarm.agent.config import config
from pyfarm.agent.http.api.base import APIResource
from pyfarm.agent.utility import JobTypeCache

logger = getLogger("agent.assign")

# Values used by the schema to do type testing
# of input requests
STRINGS = Any(*STRING_TYPES)
try:
    WHOLE_NUMBERS = Any(*(int, long))
    NUMBERS = Any(*(int, long, float, Decimal))
except NameError:  # pragma: no cover
    WHOLE_NUMBERS = int
    NUMBERS = Any(*(int, float, Decimal))

def validate_environment(values):
    """
    Ensures that ``values`` is a dictionary and that it only
    contains string keys and values.
    """
    if not isinstance(values, dict):
        raise Invalid("Expected a dictionary")

    for key, value in values.items():
        if not isinstance(key, STRING_TYPES):
            raise Invalid("Key %r must be a string" % key)

        if not isinstance(value, STRING_TYPES):
            raise Invalid("Value %r for key %r must be a string" % (key, value))

class HandlePost(object):
    def __init__(self, data):
        self.data = data

class Assign(APIResource):
    isLeaf = False  # this is not really a collection of things

    # Schemas used for validating the request before
    # the target function will handle it.  These make
    # assertions about what kind of input data is required
    # or not based on the agent's internal cod.
    SCHEMAS = {
        "POST": Schema({
            Required("job"): Schema({
                Required("id"): WHOLE_NUMBERS,
                Required("by"): NUMBERS,
                Optional("batch"): WHOLE_NUMBERS,
                Optional("user"): STRINGS,
                Optional("ram"): WHOLE_NUMBERS,
                Optional("ram_warning"): WHOLE_NUMBERS,
                Optional("ram_max"): WHOLE_NUMBERS,
                Optional("cpus"): WHOLE_NUMBERS,
                Optional("data"): dict,
                Optional("environ"): validate_environment,
                Optional("title"): STRINGS}),
            Required("jobtype"): {
                Required("name"): STRINGS,
                Required("version"): WHOLE_NUMBERS},
            Required("tasks"): lambda values: map(
                Schema({
                    Required("id"): WHOLE_NUMBERS,
                    Required("frame"): NUMBERS}), values)})}

    def post(self, **kwargs):
        request = kwargs["request"]
        data = kwargs["data"]

        # First, get the resources we have *right now*.  In some cases
        # this means using the functions in pyfarm.core.sysinfo because
        # entries in `config` could be slightly out of sync with the system.
        memory_free = int(ram_free())
        cpus = config["cpus"]
        requires_ram = data["job"].get("ram")
        requires_cpus = data["job"].get("cpus")

        # Do we have enough ram?
        if requires_ram is not None and requires_ram > memory_free:
            logger.error(
                "Task %s requires %sMB of ram, this agent has %sMB free.  "
                "Rejecting Task %s.",
                data["job"]["id"], requires_ram, memory_free, data["job"]["id"])
            request.write(
                {"error": "Not enough ram",
                 "agent_ram": memory_free,
                 "requires_ram": requires_ram})
            request.setResponseCode(BAD_REQUEST)
            request.finish()

            # touch the config
            config["free_ram"] = memory_free

        # Do we have enough cpus (count wise)?
        elif requires_cpus is not None and requires_cpus > cpus:
            logger.error(
                "Task %s requires %s CPUs, this agent has %s CPUs.  "
                "Rejecting Task %s.",
                data["job"]["id"], requires_cpus, cpus, data["job"]["id"])
            request.write(
                {"error": "Not enough cpus",
                 "agent_cpus": cpus,
                 "requires_cpus": requires_cpus})
            request.setResponseCode(BAD_REQUEST)
            request.finish()

        # In all other cases we have some work to do inside of
        # deferreds so we just have to respond
        else:
            request.setResponseCode(ACCEPTED)
            request.finish()

        # Retrieve the job tpe
        # TODO: the job type class should probably handle the caching
        # for us
        jobtype = data["jobtype"]
        deferred = JobTypeCache.get(jobtype["name"], jobtype["version"])
        deferred.addCallback(logger.debug)

        # TODO: Next steps as deferreds.  Some of these are done in the queue,
        #       or will be, but the information the agent has available may be
        #       more up to date and these checks are fast anyway.
        #   - ensure we have enough ram left to serve the optional requirements
        #   - check for number of cpus currently required by other jobs + this
        #     job to see if we've hit a limit
        #   - get job type (and cache if not already)
        #   - instance the job type with env, user, resource requirements, etc
        #   - execute the job type
        #   - (done) - workload handed off to job type including
        #     ram_max/ram_warning check, user -> uid conversion (or warning, on
        #     windows), etc

        return NOT_DONE_YET
<|endoftext|>


<commit_before>#!/usr/bin/python

###
# Copyright (c) 2004, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Informal notes, mostly for compatibility with other bots.  Based entirely on
nicks, it's an easy way to tell users who refuse to register notes when they
arrive later.
"""

__revision__ = "$Id$"
__author__ = ''

import supybot.plugins as plugins

import csv
import time

import supybot.log as log
import supybot.conf as conf
import supybot.utils as utils
import supybot.ircutils as ircutils
import supybot.privmsgs as privmsgs
import supybot.registry as registry
import supybot.callbacks as callbacks

def configure(advanced):
    # This will be called by setup.py to configure this module.  Advanced is
    # a bool that specifies whether the user identified himself as an advanced
    # user or not.  You should effect your configuration by manipulating the
    # registry as appropriate.
    from supybot.questions import expect, anything, something, yn
    conf.registerPlugin('Later', True)

conf.registerPlugin('Later')
conf.registerGlobalValue(conf.supybot.plugins.Later, 'maximum',
    registry.NonNegativeInteger(0, """Determines the maximum number of messages
    to be queued for a user.  If it's 0, there is no maximum."""))
conf.registerGlobalValue(conf.supybot.plugins.Later, 'private',
    registry.Boolean(False, """Determines whether users will be notified in the
    first place in which they're seen, or in private."""))

class Later(callbacks.Privmsg):
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        self.notes = ircutils.IrcDict()
        self.filename = conf.supybot.directories.data.dirize('Later.db')
        self._openNotes()

    def die(self):
        self._closeNotes()

    def _closeNotes(self):
        fd = utils.transactionalFile(self.filename)
        writer = csv.writer(fd)
        for (nick, notes) in self.notes.iteritems():
            for (time, whence, text) in notes:
                writer.writerow([nick, time, whence, text])
        fd.close()

    def _openNotes(self):
        try:
            fd = file(self.filename)
        except EnvironmentError, e:
            self.log.warning('Couldn\'t open %s: %s', self.filename, e)
            return
        reader = csv.reader(fd)
        for (nick, time, whence, text) in reader:
            self._addNote(nick, whence, text, at=float(time), maximum=0)
        fd.close()

    def _timestamp(self, when):
        #format = conf.supybot.humanTimestampFormat()
        diff = time.time() - when
        try:
            return utils.timeElapsed(diff, seconds=False) + ' ago'
        except ValueError:
            return 'just now'

    def _addNote(self, nick, whence, text, at=None, maximum=None):
        if at is None:
            at = time.time()
        if maximum is None:
            maximum = self.registryValue('maximum')
        try:
            notes = self.notes[nick]
            if maximum and len(notes) >= maximum:
                raise ValueError
            else:
                notes.append[(at, whence, text)]
        except KeyError:
            self.notes[nick] = [(at, whence, text)]

    def tell(self, irc, msg, args):
        """<nick> <text>

        Tells <nick> <text> the next time <nick> is in seen.
        """
        (nick, text) = privmsgs.getArgs(args, required=2)
        try:
            self._addNote(nick, msg.nick, text)
            irc.replySuccess()
        except ValueError:
            irc.error('That person\'s message queue is already full.')

    def doPrivmsg(self, irc, msg):
        try:
            notes = self.notes.pop(msg.nick)
            irc = callbacks.SimpleProxy(irc, msg)
            private = self.registryValue('private')
            for (when, whence, note) in notes:
                s = 'Sent %s: <%s> %s' % (self._timestamp(when), whence, note)
                irc.reply(s, private=private)
        except KeyError:
            pass

Class = Later

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
<commit_msg>Added a bit more flushing.<commit_after>#!/usr/bin/python

###
# Copyright (c) 2004, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Informal notes, mostly for compatibility with other bots.  Based entirely on
nicks, it's an easy way to tell users who refuse to register notes when they
arrive later.
"""

__revision__ = "$Id$"
__author__ = ''

import supybot.plugins as plugins

import csv
import time

import supybot.log as log
import supybot.conf as conf
import supybot.utils as utils
import supybot.ircutils as ircutils
import supybot.privmsgs as privmsgs
import supybot.registry as registry
import supybot.callbacks as callbacks

def configure(advanced):
    # This will be called by setup.py to configure this module.  Advanced is
    # a bool that specifies whether the user identified himself as an advanced
    # user or not.  You should effect your configuration by manipulating the
    # registry as appropriate.
    from supybot.questions import expect, anything, something, yn
    conf.registerPlugin('Later', True)

conf.registerPlugin('Later')
conf.registerGlobalValue(conf.supybot.plugins.Later, 'maximum',
    registry.NonNegativeInteger(0, """Determines the maximum number of messages
    to be queued for a user.  If it's 0, there is no maximum."""))
conf.registerGlobalValue(conf.supybot.plugins.Later, 'private',
    registry.Boolean(False, """Determines whether users will be notified in the
    first place in which they're seen, or in private."""))

class Later(callbacks.Privmsg):
    def __init__(self):
        callbacks.Privmsg.__init__(self)
        self.notes = ircutils.IrcDict()
        self.filename = conf.supybot.directories.data.dirize('Later.db')
        self._openNotes()

    def die(self):
        self._flushNotes()

    def _flushNotes(self):
        fd = utils.transactionalFile(self.filename)
        writer = csv.writer(fd)
        for (nick, notes) in self.notes.iteritems():
            for (time, whence, text) in notes:
                writer.writerow([nick, time, whence, text])
        fd.close()

    def _openNotes(self):
        try:
            fd = file(self.filename)
        except EnvironmentError, e:
            self.log.warning('Couldn\'t open %s: %s', self.filename, e)
            return
        reader = csv.reader(fd)
        for (nick, time, whence, text) in reader:
            self._addNote(nick, whence, text, at=float(time), maximum=0)
        fd.close()

    def _timestamp(self, when):
        #format = conf.supybot.humanTimestampFormat()
        diff = time.time() - when
        try:
            return utils.timeElapsed(diff, seconds=False) + ' ago'
        except ValueError:
            return 'just now'

    def _addNote(self, nick, whence, text, at=None, maximum=None):
        if at is None:
            at = time.time()
        if maximum is None:
            maximum = self.registryValue('maximum')
        try:
            notes = self.notes[nick]
            if maximum and len(notes) >= maximum:
                raise ValueError
            else:
                notes.append[(at, whence, text)]
        except KeyError:
            self.notes[nick] = [(at, whence, text)]
        self._flushNotes()

    def tell(self, irc, msg, args):
        """<nick> <text>

        Tells <nick> <text> the next time <nick> is in seen.
        """
        (nick, text) = privmsgs.getArgs(args, required=2)
        try:
            self._addNote(nick, msg.nick, text)
            irc.replySuccess()
        except ValueError:
            irc.error('That person\'s message queue is already full.')

    def doPrivmsg(self, irc, msg):
        try:
            notes = self.notes.pop(msg.nick)
            irc = callbacks.SimpleProxy(irc, msg)
            private = self.registryValue('private')
            for (when, whence, note) in notes:
                s = 'Sent %s: <%s> %s' % (self._timestamp(when), whence, note)
                irc.reply(s, private=private)
            self._flushNotes()
        except KeyError:
            pass

Class = Later

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
<|endoftext|>


<commit_before># THIS FILE IS GENERATED FROM THE SCIKITS.IMAGE SETUP.PY
version='0.2dev'
<commit_msg>Restore version file.<commit_after>version='unbuilt-dev'
<|endoftext|>


<commit_before>"""
    Amazon Discount Finder. All rights reserved.
    Author: Khai Hua
    Author: Nick Leeds
"""

import logging

from amazon.api import AmazonAPI
from amazon.api import AmazonException

import os
import sys; sys.path.append(
    os.path.join(os.path.dirname(__file__), "../camelcamelcamel/")
)

import gevent.monkey; gevent.monkey.patch_thread()

from AMZNGoldboxFind import AMZNGoldboxFind
from AMZNCamelScraper import AMZNCamelScraper

"""
    This is the root class.
"""
class AMZNMain:
    # Login information and opening statements
    def __init__(self):
        # Welcome the user
        print("\n**************************************************************\n"
              + "Welcome to the Amazon-Discounts Command Line Interface (v1.0)!\n"
              + "Below are prompted instructions for your login information.\n"
              + "For the case that you might have made a mistake, please press\n"
              + "CTRL+C to start over. If anything breaks, please send a request\n"
              + "to: https://github.com/cristiangonzales/Amazon-Discounts/issues.\n"
              + "Thank you!\n"
              + "**************************************************************\n")

        # Prompt for information to log into the account
        key = input("Please enter your AWS key here: ")
        secret_key = input("Please enter your AWS secret key here: ")
        asso_tag = input("Please enter your AWS associate tag here: ")
        region = input("Please enter your region here (i.e. US, FR, CN, UK,"+
                       "IN, CA, DE, JP, IT): ")
        self.amazon = AmazonAPI(
            aws_key=str(key),
            aws_secret=str(secret_key),
            aws_associate_tag=str(asso_tag),
            region=str(region).upper()
        )
        # Initialize our cart to be pointing at nothing, at first
        self.cart = None

        # Ask for the discount rate, the output file, and the option
        discount = int(input("\nPlease enter the discount you would like to query for\n"
                         + "(enter a whole number 1-99): "))
        # Check to see if it is actually an integer between 0-99
        if (not isinstance(discount, int)) or discount < 1 or discount > 99:
            raise Exception("Sorry, you did not enter a number between 1 and 99!")

        # Prompt the user for an output text file to write to
        outputFile = open("../../amazon-discounts.log", 'w')
        errorLog = open("../../failed-proxies.log", 'w')
        # Optional argument as determined by the user
        optionArg = input("Please enter a type of item you wish to search for!\n"
                          + "If you do not have a particular type of item,\n"
                          + "then enter a number so that we can search Goldbox\n"
                          + "for you and look for discounts there. Also, if you\n"
                          + "wish to search for a single item, you may enter the\n"
                          + "keywords 'item lookup': ")

        # For this option argument, if it is not a keyword, then scrape Amazon's Goldbox page,
        # and if not, then search for that item through the API. Though, if a user inputs "item
        # lookup", then simply make a single list with that ASIN number appended.
        if optionArg.isdigit():
            asinList = AMZNGoldboxFind().scrape_goldbox(int(optionArg))
        elif optionArg.replace(" ", "").upper() == "ITEMLOOKUP":
            singleItem = str(input("\nPlease enter in the ASIN number of the item you\n"
                                 + "wish to look for here: "))
            asinList = []
            asinList.append(singleItem)
        else:
            asinList = self.item_search(optionArg, discount)

        # Cart count used in the final conditional. We will do a comparison. If the counter is 0, then we will
        # use the cart_create method to create the cart, and if not, then we will use the cart_add method
        # to add to the existing cart.
        cartCount = 0
        # Iterate through the entire ASIN list to do the appropriate comparisons to see if that ASIN meets our
        # discount, and then we will write to the file
        for asin in asinList:
            # Here, we do an item lookup and get the title, current price, and offer url for this ASIN
            # In the case that the item is too low, then it will simply continue onto the next iteration
            # of the loop.
            try:
                titlePriceURLList = self.item_lookup(str(asin))
            except:
                continue
            title = titlePriceURLList[0]
            currentPrice = titlePriceURLList[1]
            offerUrl = titlePriceURLList[2]
            # Interfacing with the CamelCamelCamel scraper, passing in the ASIN number and attempting to
            # get the average price for that ASIN number. Should it be the case that the average price
            # is none, then we will check for it and see if it is accordingly.
            camelPriceHistory = AMZNCamelScraper().AccessASIN(asin)
            averagePrice = camelPriceHistory.get_average_price()
            # Conditional here to see if the average price is NoneType. If it is, it did not connect so we
            # should move onto the next ASIN (continue to the next iteration)
            if averagePrice is None:
                errorLog.write("Proxy tunneling failed at " + str(camelPriceHistory.get_proxy()))
                continue
            # Now, we check the discount, sending the average price, the current price, and the user's discount
            if self.check_discount(discount, averagePrice, currentPrice):
                outputFile.write("Title: " + title +
                                "\nAmazon price: " + str(currentPrice) +
                                "\nCamelCamelCamel average discount price: " + str(averagePrice) +
                                "\nURL: " + offerUrl + "\n\n")
                # Logic to add this item to our cart (if it is our first time adding to the cart, then create
                # the cart item, and if it isn't the first time, then we simply add to the existiing cart
                if cartCount == 0:
                    self.cart_create(str(asin))
                    cartCount = cartCount + 1
                else:
                    self.cart_add(str(asin))
        # Write the purchase URL to the logging file
        if cartCount != 0:
            outputFile.write("Click here to add all items to your cart: " + str(self.cart.purchase_url))
        else:
            outputFile.write("Sorry, we did not find any items to add to your cart!")
        # Once we are done, close the output file and error log, and say goodbye.
        errorLog.close()
        outputFile.close()
        print("\nThank you for using the Amazon-Discounts CLI! Exiting now...\n")

    """
        Item search in the case that the user requests a query.
        :param keywords: The keyword that the user requests in the case that they want to query Amazon, as a string.
        :param pctoff: The percent off that the user requests, as an integer.
        :return: A list of ASIN values, as strings
    """
    def item_search(self, keywords, pctoff):
        try:
            result = self.amazon.search(
                SearchIndex='All', Keywords=keywords, MinPercentageOff=pctoff)
            asinList = []
            for i, product in enumerate(result):
                asinList.append(product.asin)
                # logging.debug(product.asin)
            return asinList
        except Exception as e:
            raise AmazonException(str(e))

    """
        Item lookup for the specified ASIN number.
        :param ASIN: The ASIN number, as a string.
        :return amznList [title, price, URL] as an array. Title is a string, price as a float, and URL as a string
    """
    def item_lookup(self, ASIN):
        try:
            # Initialize the Amazon list
            amznList = []
            # Lookup the result based on ASIN
            result = self.amazon.lookup(ItemId=ASIN)
            # Append the appropriate values
            amznList.append(result.title)
            # logging.debug(result.title)
            amznList.append(
                float(result.formatted_price.replace('$', ''))
            )
            # logging.debug(result.formatted_price.replace('$', ''))
            amznList.append(result.offer_url)
            # logging.debug(result.offer_url)
            # Return the list to the caller
            return amznList
        except Exception as e:
            raise AmazonException(str(e))

    """
        Check to see if this is a discount, as wanted by the user.
        :return bool (dependent on if this is a discount that we want or not).
    """
    def check_discount(self, discount, averagePrice, price):
        return (100 - (float(price) / float(averagePrice) * 100)) >= float(discount)

    """
        Once we do all the appropriate comparisons, we will create the cart and
         add this item to the user's cart (this method is only used on the first ASIN).
        :param ASIN: The ASIN number of the discounted item
        :return void
    """
    def cart_create(self, ASIN):
        try:
            product = self.amazon.lookup(ItemId=ASIN)
            item = {'offer_id': product.offer_id, 'quantity': 1}
            self.cart = self.amazon.cart_create(item)
        except Exception as e:
            raise AmazonException(str(e))

    """
        This method is instantiated if we have already created the cart, and we wish to add to it.
        :param ASIN: The ASIN number of the discounted item
        :return void
    """
    def cart_add(self, ASIN):
        try:
            product = self.amazon.lookup(ItemId=ASIN)
            item = {'offer_id': product.offer_id, 'quantity': 1}
            self.amazon.cart_add(item, self.cart.cart_id, self.cart.hmac)
        except Exception as e:
            raise AmazonException(str(e))

if __name__ == "__main__":

    # Setting root logging level to DEBUG
    # logging.getLogger().setLevel(logging.DEBUG)

    # Multiple except blocks to see if the user does a keyboard interrupt, or if something breaks in the code.
    try:
        AMZNMain()
    except KeyboardInterrupt:
        print("\nExiting the CLI now...\n")
        sys.exit(1)
    except Exception as e:
        logging.error(str(e) +
                      "\nOops, sorry! Something seems to be broken." +
                      "\nPlease submit a fix request here: " +
                      "\nhttps://github.com/cristiangonzales/Amazon-Discounts/issues")
        sys.exit(1)
<commit_msg>prints percentage off<commit_after>"""
    Amazon Discount Finder. All rights reserved.
    Author: Khai Hua
    Author: Nick Leeds
"""

import logging

from amazon.api import AmazonAPI
from amazon.api import AmazonException

import os
import sys
sys.path.append(
    os.path.join(os.path.dirname(__file__), "../camelcamelcamel/")
)

import gevent.monkey
gevent.monkey.patch_thread()

from AMZNGoldboxFind import AMZNGoldboxFind
from AMZNCamelScraper import AMZNCamelScraper

"""
    This is the root class.
"""

class AMZNMain:
    # Login information and opening statements
    def __init__(self):
        # Welcome the user
        print("\n**************************************************************\n"
              + "Welcome to the Amazon-Discounts Command Line Interface (v1.0)!\n"
              + "Below are prompted instructions for your login information.\n"
              + "For the case that you might have made a mistake, please press\n"
              + "CTRL+C to start over. If anything breaks, please send a request\n"
              + "to: https://github.com/cristiangonzales/Amazon-Discounts/issues.\n"
              + "Thank you!\n"
              + "**************************************************************\n")

        # Prompt for information to log into the account
        key = input("Please enter your AWS key here: ")
        secret_key = input("Please enter your AWS secret key here: ")
        asso_tag = input("Please enter your AWS associate tag here: ")
        region = input("Please enter your region here (i.e. US, FR, CN, UK," +
                       "IN, CA, DE, JP, IT): ")
        self.amazon = AmazonAPI(
            aws_key=str(key),
            aws_secret=str(secret_key),
            aws_associate_tag=str(asso_tag),
            region=str(region).upper()
        )
        # Initialize our cart to be pointing at nothing, at first
        self.cart = None

        # Ask for the discount rate, the output file, and the option
        discount = int(input("\nPlease enter the discount you would like to query for\n"
                             + "(enter a whole number 1-99): "))
        # Check to see if it is actually an integer between 0-99
        if (not isinstance(discount, int)) or discount < 1 or discount > 99:
            raise Exception("Sorry, you did not enter a number between 1 and 99!")

        # Prompt the user for an output text file to write to
        outputFile = open("../../amazon-discounts.log", 'w')
        errorLog = open("../../failed-proxies.log", 'w')
        # Optional argument as determined by the user
        optionArg = input("Please enter a type of item you wish to search for!\n"
                          + "If you do not have a particular type of item,\n"
                          + "then enter a number so that we can search Goldbox\n"
                          + "for you and look for discounts there. Also, if you\n"
                          + "wish to search for a single item, you may enter the\n"
                          + "keywords 'item lookup': ")

        # For this option argument, if it is not a keyword, then scrape Amazon's Goldbox page,
        # and if not, then search for that item through the API. Though, if a user inputs "item
        # lookup", then simply make a single list with that ASIN number appended.
        if optionArg.isdigit():
            asinList = AMZNGoldboxFind().scrape_goldbox(int(optionArg))
        elif optionArg.replace(" ", "").upper() == "ITEMLOOKUP":
            singleItem = str(input("\nPlease enter in the ASIN number of the item you\n"
                                   + "wish to look for here: "))
            asinList = []
            asinList.append(singleItem)
        else:
            asinList = self.item_search(optionArg, discount)

        # Cart count used in the final conditional. We will do a comparison. If the counter is 0, then we will
        # use the cart_create method to create the cart, and if not, then we will use the cart_add method
        # to add to the existing cart.
        cartCount = 0
        # Iterate through the entire ASIN list to do the appropriate comparisons to see if that ASIN meets our
        # discount, and then we will write to the file
        for asin in asinList:
            # Here, we do an item lookup and get the title, current price, and offer url for this ASIN
            # In the case that the item is too low, then it will simply continue onto the next iteration
            # of the loop.
            try:
                titlePriceURLList = self.item_lookup(str(asin))
            except:
                continue
            title = titlePriceURLList[0]
            currentPrice = titlePriceURLList[1]
            offerUrl = titlePriceURLList[2]
            # Interfacing with the CamelCamelCamel scraper, passing in the ASIN number and attempting to
            # get the average, lowest, and highest price for that ASIN number. Should it be the case that
            # the average price is none, then we will check for it and see if it is accordingly.
            camelPriceHistory = AMZNCamelScraper().AccessASIN(asin)
            averagePrice = camelPriceHistory.get_average_price()
            lowestPrice = camelPriceHistory.get_lowest_price()
            highestPrice = camelPriceHistory.get_highest_price()
            # Conditional here to see if the average price is NoneType. If it is, it did not connect so we
            # should move onto the next ASIN (continue to the next iteration)
            if averagePrice is None:
                errorLog.write("Proxy tunneling failed at " + str(camelPriceHistory.get_proxy()))
                continue
            # Now, we check the discount, sending the average price, the current price, and the user's discount
            if self.check_discount(discount, averagePrice, currentPrice):
                percentageOff = self.calculate_percentage_off(averagePrice, currentPrice)
                outputFile.write("Title: " + title +
                                 "\nAmazon price: " + str(currentPrice) +
                                 "\nCamelCamelCamel average price: " + str(averagePrice) +
                                 "\nCamelCamelCamel highest price: " + str(highestPrice) +
                                 "\nCamelCamelCamel lowest price: " + str(lowestPrice) +
                                 "\nPercentage off from average: " + str(percentageOff) +
                                 "\nURL: " + offerUrl + "\n\n")
                # Logic to add this item to our cart (if it is our first time adding to the cart, then create
                # the cart item, and if it isn't the first time, then we simply add to the existiing cart
                if cartCount == 0:
                    self.cart_create(str(asin))
                    cartCount = cartCount + 1
                else:
                    self.cart_add(str(asin))
        # Write the purchase URL to the logging file
        if cartCount != 0:
            outputFile.write("Click here to add all items to your cart: " + str(self.cart.purchase_url))
        else:
            outputFile.write("Sorry, we did not find any items to add to your cart!")
        # Once we are done, close the output file and error log, and say goodbye.
        errorLog.close()
        outputFile.close()
        print("\nThank you for using the Amazon-Discounts CLI! Exiting now...\n")

    """
        Item search in the case that the user requests a query.
        :param keywords: The keyword that the user requests in the case that they want to query Amazon, as a string.
        :param pctoff: The percent off that the user requests, as an integer.
        :return: A list of ASIN values, as strings
    """

    def item_search(self, keywords, pctoff):
        try:
            result = self.amazon.search(
                SearchIndex='All', Keywords=keywords, MinPercentageOff=pctoff)
            asinList = []
            for i, product in enumerate(result):
                asinList.append(product.asin)
                # logging.debug(product.asin)
            return asinList
        except Exception as e:
            raise AmazonException(str(e))

    """
        Item lookup for the specified ASIN number.
        :param ASIN: The ASIN number, as a string.
        :return amznList [title, price, URL] as an array. Title is a string, price as a float, and URL as a string
    """

    def item_lookup(self, ASIN):
        try:
            # Initialize the Amazon list
            amznList = []
            # Lookup the result based on ASIN
            result = self.amazon.lookup(ItemId=ASIN)
            # Append the appropriate values
            amznList.append(result.title)
            # logging.debug(result.title)
            amznList.append(
                float(result.formatted_price.replace('$', ''))
            )
            # logging.debug(result.formatted_price.replace('$', ''))
            amznList.append(result.offer_url)
            # logging.debug(result.offer_url)
            # Return the list to the caller
            return amznList
        except Exception as e:
            raise AmazonException(str(e))

    """
        Check to see if this is a discount, as wanted by the user.
        :return bool (dependent on if this is a discount that we want or not).
    """

    def check_discount(self, discount, averagePrice, price):
        return (100 - (float(price) / float(averagePrice) * 100)) >= float(discount)

    """
        calculate the percentage off the current Amazon price is from the average price.
        :return int
    """

    def calculate_percentage_off(self, averagePrice, price):
        return int(100 - (float(price) / float(averagePrice) * 100))

    """
        Once we do all the appropriate comparisons, we will create the cart and
         add this item to the user's cart (this method is only used on the first ASIN).
        :param ASIN: The ASIN number of the discounted item
        :return void
    """

    def cart_create(self, ASIN):
        try:
            product = self.amazon.lookup(ItemId=ASIN)
            item = {'offer_id': product.offer_id, 'quantity': 1}
            self.cart = self.amazon.cart_create(item)
        except Exception as e:
            raise AmazonException(str(e))

    """
        This method is instantiated if we have already created the cart, and we wish to add to it.
        :param ASIN: The ASIN number of the discounted item
        :return void
    """

    def cart_add(self, ASIN):
        try:
            product = self.amazon.lookup(ItemId=ASIN)
            item = {'offer_id': product.offer_id, 'quantity': 1}
            self.amazon.cart_add(item, self.cart.cart_id, self.cart.hmac)
        except Exception as e:
            raise AmazonException(str(e))

if __name__ == "__main__":

    # Setting root logging level to DEBUG
    # logging.getLogger().setLevel(logging.DEBUG)

    # Multiple except blocks to see if the user does a keyboard interrupt, or if something breaks in the code.
    try:
        AMZNMain()
    except KeyboardInterrupt:
        print("\nExiting the CLI now...\n")
        sys.exit(1)
    except Exception as e:
        logging.error(str(e) +
                      "\nOops, sorry! Something seems to be broken." +
                      "\nPlease submit a fix request here: " +
                      "\nhttps://github.com/cristiangonzales/Amazon-Discounts/issues")
        sys.exit(1)
<|endoftext|>


<commit_before># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# 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.

"""
Network
-------

Returns information about the network including ip address, dns, data
sent/received, and some error information.

:const IP_PRIVATE:
    set of private class A, B, and C network ranges

    .. seealso::
        :rfc:`1918`

:const IP_NONNETWORK:
    set of non-network address ranges including all of the
    above constants except the :const:`IP_PRIVATE`
"""

import socket

import netifaces
from netaddr import IPSet, IPNetwork, IPAddress

from pyfarm.core.logger import getLogger

logger = getLogger("agent.dns")

IP_PRIVATE = IPSet([
    IPNetwork("10.0.0.0/8"),
    IPNetwork("172.16.0.0/12"),
    IPNetwork("192.168.0.0/16")])
IP_NONNETWORK = IPSet([
    IPNetwork("0.0.0.0/8"),         # special use
    IPNetwork("169.254.0.0/16"),    # link local
    IPNetwork("127.0.0.0/8"),       # loopback
    IPNetwork("224.0.0.0/4"),       # multicast
    IPNetwork("255.255.255.255")])  # broadcast

def mac_addresses(exclude=("00:00:00:00:00:00", )):
    """
    Returns a tuple of all mac addresses on the system

    :param tuple exclude:
        Specific mac mac addresses that should not be returned from
        this function.  By default this is '00:00:00:00:00:00'.
    """
    results = set()
    for interface in map(netifaces.ifaddresses, netifaces.interfaces()):
        for address in interface.get(netifaces.AF_LINK, []):
            mac = address.get("addr")
            if mac is None or mac in exclude:
                continue

            results.add(mac)

    return tuple(results)

def hostname(trust_name_from_ips=True):
    """
    Returns the hostname which the agent should send to the master.

    :param bool trust_resolved_name:
        If True and all addresses provided by :func:`addresses` resolve
        to a single hostname then just return that name as it's the most
        likely hostname to be accessible by the rest of the network.
    """
    logger.debug("Attempting to discover the hostname")

    # For every address retrieve the hostname we can resolve it
    # to.  We'll use this set later to compare again what the system
    # is telling is the hostname should be.
    reverse_hostnames = set()
    for address in addresses():
        try:
            dns_name, aliases, dns_addresses = socket.gethostbyaddr(address)

        except socket.herror:  # pragma: no cover
            logger.warning(
                "Could not resolve %s to a hostname using DNS.", address)
        else:
            if address in dns_addresses:
                reverse_hostnames.add(dns_name)
                logger.debug(
                    "Lookup of %s resolved to %s", address, dns_name)

    # If all the addresses we know about map to a single
    # hostname then just return that instead of continuing
    # on.  We do this because the DNS system should know better
    # than the local system will what the fully qualified
    # hostname would be.  This is especially true on some
    # platforms where the local DNS implementation seems to
    # produce the wrong information when resolving the local
    # hostname.
    if len(reverse_hostnames) == 1 and trust_name_from_ips:
        return reverse_hostnames.pop()

    if not reverse_hostnames:
        logger.warning(
            "DNS failed to resolve %s to hostnames", list(addresses()))

    local_hostname = socket.gethostname()
    local_fqdn_query = socket.getfqdn()

    if local_fqdn_query in reverse_hostnames:
        name = local_fqdn_query

    elif local_hostname in reverse_hostnames:
        name = local_hostname

    else:
        # If neither local_hostname or local_fqdn_query seemed
        # to map to a known hostname from an IP address then
        # get the FQDN of the locally provided hostname as
        # a fallback.
        name = socket.getfqdn(local_hostname)

    if name.startswith("localhost"):
        logger.warning("Hostname resolved to or contains 'localhost'")

    if name.endswith(".local"):
        logger.warning(
            "Operating system '.local' to hostname.  In some cases, most "
            "often on OS X, this may cause unexpected problems reaching this "
            "host by name on the network.  Manually setting the hostname "
            "with --hostname may be advisable.")

    return name

def addresses(private_only=True):
    """Returns a tuple of all non-local ip addresses."""
    results = set()

    for interface in interfaces():
        addrinfo = netifaces.ifaddresses(interface)
        for address in addrinfo.get(socket.AF_INET, []):
            addr = address.get("addr")

            if addr is not None:
                # Make sure that what we're getting out of
                # netifaces is something we can use.
                try:
                    ip = IPAddress(addr)
                except ValueError:  # pragma: no cover
                    logger.error(
                        "Could not convert %s to a valid IP object" % addr)
                else:
                    if ip in IP_PRIVATE or not private_only:
                        results.add(addr)

    if not addresses:  # pragma: no cover
        logger.error("No addresses could be found")

    return tuple(results)

def interfaces():
    """Returns the names of all valid network interface names"""
    results = set()

    for name in netifaces.interfaces():
        # only add network interfaces which have IPv4
        addresses = netifaces.ifaddresses(name)

        if socket.AF_INET not in addresses:  # pragma: no cover
            continue

        if any(addr.get("addr") for addr in addresses[socket.AF_INET]):
            results.add(name)

    if not results:  # pragma: no cover
        logger.warning("Failed to find any interfaces")

    return tuple(results)

<commit_msg>fix, addresses is no longer a generator<commit_after># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# 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.

"""
Network
-------

Returns information about the network including ip address, dns, data
sent/received, and some error information.

:const IP_PRIVATE:
    set of private class A, B, and C network ranges

    .. seealso::
        :rfc:`1918`

:const IP_NONNETWORK:
    set of non-network address ranges including all of the
    above constants except the :const:`IP_PRIVATE`
"""

import socket

import netifaces
from netaddr import IPSet, IPNetwork, IPAddress

from pyfarm.core.logger import getLogger

logger = getLogger("agent.dns")

IP_PRIVATE = IPSet([
    IPNetwork("10.0.0.0/8"),
    IPNetwork("172.16.0.0/12"),
    IPNetwork("192.168.0.0/16")])
IP_NONNETWORK = IPSet([
    IPNetwork("0.0.0.0/8"),         # special use
    IPNetwork("169.254.0.0/16"),    # link local
    IPNetwork("127.0.0.0/8"),       # loopback
    IPNetwork("224.0.0.0/4"),       # multicast
    IPNetwork("255.255.255.255")])  # broadcast

def mac_addresses(exclude=("00:00:00:00:00:00", )):
    """
    Returns a tuple of all mac addresses on the system

    :param tuple exclude:
        Specific mac mac addresses that should not be returned from
        this function.  By default this is '00:00:00:00:00:00'.
    """
    results = set()
    for interface in map(netifaces.ifaddresses, netifaces.interfaces()):
        for address in interface.get(netifaces.AF_LINK, []):
            mac = address.get("addr")
            if mac is None or mac in exclude:
                continue

            results.add(mac)

    return tuple(results)

def hostname(trust_name_from_ips=True):
    """
    Returns the hostname which the agent should send to the master.

    :param bool trust_resolved_name:
        If True and all addresses provided by :func:`addresses` resolve
        to a single hostname then just return that name as it's the most
        likely hostname to be accessible by the rest of the network.
    """
    logger.debug("Attempting to discover the hostname")

    # For every address retrieve the hostname we can resolve it
    # to.  We'll use this set later to compare again what the system
    # is telling is the hostname should be.
    reverse_hostnames = set()
    ip_addresses = addresses()
    for address in ip_addresses:
        try:
            dns_name, aliases, dns_addresses = socket.gethostbyaddr(address)

        except socket.herror:  # pragma: no cover
            logger.warning(
                "Could not resolve %s to a hostname using DNS.", address)
        else:
            if address in dns_addresses:
                reverse_hostnames.add(dns_name)
                logger.debug(
                    "Lookup of %s resolved to %s", address, dns_name)

    # If all the addresses we know about map to a single
    # hostname then just return that instead of continuing
    # on.  We do this because the DNS system should know better
    # than the local system will what the fully qualified
    # hostname would be.  This is especially true on some
    # platforms where the local DNS implementation seems to
    # produce the wrong information when resolving the local
    # hostname.
    if len(reverse_hostnames) == 1 and trust_name_from_ips:
        return reverse_hostnames.pop()

    if not reverse_hostnames:
        logger.warning(
            "DNS failed to resolve %s to hostnames", ip_addresses)

    local_hostname = socket.gethostname()
    local_fqdn_query = socket.getfqdn()

    if local_fqdn_query in reverse_hostnames:
        name = local_fqdn_query

    elif local_hostname in reverse_hostnames:
        name = local_hostname

    else:
        # If neither local_hostname or local_fqdn_query seemed
        # to map to a known hostname from an IP address then
        # get the FQDN of the locally provided hostname as
        # a fallback.
        name = socket.getfqdn(local_hostname)

    if name.startswith("localhost"):
        logger.warning("Hostname resolved to or contains 'localhost'")

    if name.endswith(".local"):
        logger.warning(
            "Operating system '.local' to hostname.  In some cases, most "
            "often on OS X, this may cause unexpected problems reaching this "
            "host by name on the network.  Manually setting the hostname "
            "with --hostname may be advisable.")

    return name

def addresses(private_only=True):
    """Returns a tuple of all non-local ip addresses."""
    results = set()

    for interface in interfaces():
        addrinfo = netifaces.ifaddresses(interface)
        for address in addrinfo.get(socket.AF_INET, []):
            addr = address.get("addr")

            if addr is not None:
                # Make sure that what we're getting out of
                # netifaces is something we can use.
                try:
                    ip = IPAddress(addr)
                except ValueError:  # pragma: no cover
                    logger.error(
                        "Could not convert %s to a valid IP object" % addr)
                else:
                    if ip in IP_PRIVATE or not private_only:
                        results.add(addr)

    if not addresses:  # pragma: no cover
        logger.error("No addresses could be found")

    return tuple(results)

def interfaces():
    """Returns the names of all valid network interface names"""
    results = set()

    for name in netifaces.interfaces():
        # only add network interfaces which have IPv4
        addresses = netifaces.ifaddresses(name)

        if socket.AF_INET not in addresses:  # pragma: no cover
            continue

        if any(addr.get("addr") for addr in addresses[socket.AF_INET]):
            results.add(name)

    if not results:  # pragma: no cover
        logger.warning("Failed to find any interfaces")

    return tuple(results)

<|endoftext|>


<commit_before>import luigi
import time
import random
import string

# ==============================================================================

class TargetInfoParameter(luigi.Parameter):
    pass

# ==============================================================================

class TargetInfo(object):
    '''
    Class to be used for sending specification of which target, from which
    task, to use, when stitching workflow tasks' outputs and inputs together.
    '''
    task = None
    path = None
    target = None

    def __init__(self, task, path):
        self.task = task
        self.path = path
        self.target = luigi.LocalTarget(path)

    def open(self, *args, **kwargs):
        return self.target.open(*args, **kwargs)

# ==============================================================================

class DependencyHelpers():
    '''
    Mixin implementing methods for supporting dynamic, and target-based
    workflow definition, as opposed to the task-based one in vanilla luigi.
    '''

    # --------------------------------------------------------
    # Handle inputs
    # --------------------------------------------------------

    def requires(self):
        return self._upstream_tasks()

    def _upstream_tasks(self):
        upstream_tasks = []
        for attrname, attrval in self.__dict__.iteritems():
            if 'in_' == attrname[0:3]:
                if callable(attrval):
                    upstream_tasks.append(attrval().task)
                elif isinstance(attrval, list):
                    upstream_tasks.extend([x().task for x in attrval if callable(x)])
                else:
                    raise Exception('Attribute with name pattern "in_*" was neither callable nor list')
        return upstream_tasks

    # --------------------------------------------------------
    # Handle outputs
    # --------------------------------------------------------

    def output(self):
        return self._output_targets()

    def _output_targets(self):
        outputs = []
        for attrname in dir(self):
            if callable(getattr(self, attrname)) and 'out_' in attrname:
                outputs.append(getattr(self, attrname)().target)
        return outputs
<commit_msg>Add support for lists of TargetInfos in in- and outports<commit_after>import luigi
import time
import random
import string

# ==============================================================================

class TargetInfoParameter(luigi.Parameter):
    pass

# ==============================================================================

class TargetInfo(object):
    '''
    Class to be used for sending specification of which target, from which
    task, to use, when stitching workflow tasks' outputs and inputs together.
    '''
    task = None
    path = None
    target = None

    def __init__(self, task, path):
        self.task = task
        self.path = path
        self.target = luigi.LocalTarget(path)

    def open(self, *args, **kwargs):
        return self.target.open(*args, **kwargs)

# ==============================================================================

class DependencyHelpers():
    '''
    Mixin implementing methods for supporting dynamic, and target-based
    workflow definition, as opposed to the task-based one in vanilla luigi.
    '''

    # --------------------------------------------------------
    # Handle inputs
    # --------------------------------------------------------

    def requires(self):
        return self._upstream_tasks()

    def _upstream_tasks(self):
        upstream_tasks = []
        for attrname, attrval in self.__dict__.iteritems():
            if 'in_' == attrname[0:3]:
                if callable(attrval):
                    val = attrval()
                    if isinstance(val, TargetInfo):
                        upstream_tasks.append(val.task)
                    elif isinstance(val, list):
                        for item in val:
                            if callable(item):
                                upstream_tasks.append(item().task)
                            elif isinstance(item, TargetInfo):
                                upstream_tasks.append(item.task)
                            else:
                                raise Exception('Item %s returned by %s is neither callable nor list!' % (item, attrval))
                else:
                    raise Exception('Attribute %s is not callable!' % attrname)
        return upstream_tasks

    # --------------------------------------------------------
    # Handle outputs
    # --------------------------------------------------------

    def output(self):
        return self._output_targets()

    def _output_targets(self):
        outputs = []
        for attrname in dir(self):
            attrval = getattr(self, attrname)
            if attrname[0:4] == 'out_':
                # Function returning list of TargetInfos
                if callable(attrval):
                    val = attrval()
                    if isinstance(val, TargetInfo):
                        outputs.append(val.target)
                    elif isinstance(val, list):
                        for item in val:
                            if callable(item):
                                outputs.append(item().target)
                            elif isinstance(item, TargetInfo):
                                outputs.append(item.target)
                            else:
                                raise Exception('Item in list returned by %s neither function nor TargetInfo!' % attrname)
                else:
                    raise Exception('Attribute %s is not callable!' % attrname)

        return outputs
<|endoftext|>


<commit_before>'''create ColumnsTable that can be printed in a txt file

    c = ColumnsTable(('colname', 6, '%6.2f', ('header1', 'header2'), 'legend'),)
    c.append(colname=10)  # also other column name=value pairs
    ...
    c.append_legend()
    r = Report()
    r.append('title')
    c.iterate_lines(lambda line: r.append(line))  # also takes kwds
    r.write(path)
'''

import collections
import pdb
import unittest

ColumnsTableFields = collections.namedtuple('ColumnsTableFields', 'name width formatter headers legend')

class ColumnsTable(object):
    def __init__(self, columns, verbose=False):
        'columns is an iterable with elements (name, width, formatter, (header-list), legend)'
        self._columns = []
        self._number_of_header_lines = 0
        self._column_names = set()
        for column_def in columns:
            assert len(column_def) == 5, 'column_def must have length 5: %s' % str(column_def)
            ctf = ColumnsTableFields(*column_def)
            headers = ctf.headers
            # convert 'abc' to ('abc',)
            headers = (headers,) if isinstance(headers, str) else headers
            if self._number_of_header_lines != 0:
                if self._number_of_header_lines != len(headers):
                    print '+++++++++++++++++++++'
                    print 'inconsistent number of lines in header'
                    print 'have both %d and %d' % (self._number_of_header_lines, len(headers))
                    print 'found at headesr: %s' % headers
                    print '+++++++++++++++++++++'
                    pdb.set_trace()
            self._number_of_header_lines = len(headers)
            self._columns.append(ColumnsTableFields(ctf.name,
                                                    ctf.width,
                                                    ctf.formatter,
                                                    headers,
                                                    ctf.legend))
            if ctf.name in self._column_names:
                print '+++++++++++++++++++++'
                print 'column name already defined: %s' % ctf.name
                print '+++++++++++++++++++++'
                pdb.set_trace()
            self._column_names.add(ctf.name)
        self._lines = []
        self._verbose = verbose
        self._header()

    def append_legend(self):
        def cat_headers(headers):
            text = ''
            for header in headers:
                if len(text) > 0:
                    text += ' '
                text += header.strip()
            return text

        self._append_line(' ')
        self._append_line('column legend:')
        for cd in self._columns:
            line = '%12s -> %s' % (cat_headers(cd.headers), cd.legend)
            self._append_line(line)

    def iterlines(self):
        for line in self._lines:
            yield line

    def append_detail(self, **kwds):
        line = ''
        # detect and report extra arguments
        for k in kwds.keys():
            if k not in self._column_names:
                print 'Column %s is not defined in the ColumnTable' % k
                print 'defined column names are', self._column_names
                pdb.set_trace()
        # format values in kwds
        for cd in self._columns:
            if cd.name in kwds and kwds[cd.name] is not None:
                try:
                    glyph = cd.formatter % kwds[cd.name]
                except TypeError as e:
                    print '+++++++++++++++++++++++++'
                    print TypeError, e
                    print 'formatter  : %s' % cd.formatter
                    print 'field name : %s' % cd.name
                    print 'field value: %s' % kwds[cd.name]
                    print '+++++++++++++++++++++++++'
                    pdb.set_trace()
            else:
                # if missing or None, print spaces for the value
                glyph = ' ' * cd.width
            if len(line) > 0:
                line += ' '
            line += glyph
        self._append_line(line)

    def _append_line(self, line):
        if self._verbose:
            print line
        self._lines.append(line)

    def _header(self):
        def append_header(index):
            line = ''
            for cd in self._columns:
                formatter = '%' + str(cd.width) + 's'
                formatted = formatter % cd.headers[index]
                line += (' ' if len(line) > 0 else '') + formatted
            self._append_line(line)

        for header_line_index in xrange(self._number_of_header_lines):
            append_header(header_line_index)

    def _print(self):
        for line in self._lines:
            print line

class TestColumnsTable(unittest.TestCase):
    def setUp(self,):
        self.verbose = False
        self.columns = ColumnsTable(
            (('a', 3, '%3d', ('one', 'num'), 'angstroms'),
             ('bcd', 10, '%10.2f', ('length', 'meters'), 'something really big'),
             ),
            verbose=self.verbose,
        )

    def test_construction(self):
        self.assertTrue(True)
        pass  # tested in setUp()

    def test_append_detail_line(self):
        c = self.columns
        c.append_detail(a=10, bcd=20)
        c.append_detail(bcd=30)
        c.append_detail(a=50)
        self.assertEqual(len(c._lines), 5)
        self.assertTrue(True)

    def test_iterlines(self):
        c = self.columns
        counter = collections.Counter()

        for line in c.iterlines():
            counter['line'] += 1
            if self.verbose:
                print line

        self.assertEqual(counter['line'], 2)  # only 2 column headers lines

    def test_append_legend_lines(self):
        c = self.columns
        c.append_legend()
        self.assertTrue(True)

    def test_one_header(self):
        c = ColumnsTable(
            (('a', 10, '%d', 'first', 'words'),
             ('b', 20, '%d', 'second', 'more words'),
             )
        )
        self.assertEqual(c._number_of_header_lines, 1)

if __name__ == '__main__':
    unittest.main()
    if False:
        pdb
<commit_msg>fix legend formatting<commit_after>'''create ColumnsTable that can be printed in a txt file

    c = ColumnsTable(('colname', 6, '%6.2f', ('header1', 'header2'), 'legend'),)
    c.append(colname=10)  # also other column name=value pairs
    ...
    c.append_legend()
    r = Report()
    r.append('title')
    c.iterate_lines(lambda line: r.append(line))  # also takes kwds
    r.write(path)
'''

import collections
import pdb
import unittest

ColumnsTableFields = collections.namedtuple('ColumnsTableFields', 'name width formatter headers legend')

class ColumnsTable(object):
    def __init__(self, columns, verbose=False):
        'columns is an iterable with elements (name, width, formatter, (header-list), legend)'
        self._columns = []
        self._number_of_header_lines = 0
        self._column_names = set()
        for column_def in columns:
            assert len(column_def) == 5, 'column_def must have length 5: %s' % str(column_def)
            ctf = ColumnsTableFields(*column_def)
            headers = ctf.headers
            # convert 'abc' to ('abc',)
            headers = (headers,) if isinstance(headers, str) else headers
            if self._number_of_header_lines != 0:
                if self._number_of_header_lines != len(headers):
                    print '+++++++++++++++++++++'
                    print 'inconsistent number of lines in header'
                    print 'have both %d and %d' % (self._number_of_header_lines, len(headers))
                    print 'found at headesr: %s' % headers
                    print '+++++++++++++++++++++'
                    pdb.set_trace()
            self._number_of_header_lines = len(headers)
            self._columns.append(ColumnsTableFields(ctf.name,
                                                    ctf.width,
                                                    ctf.formatter,
                                                    headers,
                                                    ctf.legend))
            if ctf.name in self._column_names:
                print '+++++++++++++++++++++'
                print 'column name already defined: %s' % ctf.name
                print '+++++++++++++++++++++'
                pdb.set_trace()
            self._column_names.add(ctf.name)
        self._lines = []
        self._verbose = verbose
        self._header()

    def append_legend(self):
        def cat_headers(headers):
            text = ''
            for header in headers:
                if len(text) > 0:
                    text += ' '
                text += header.strip()
            return text

        self._append_line(' ')
        self._append_line('column legend:')
        for cd in self._columns:
            # TODO: replace fix width below with value detemined from column headings
            line = '%13s -> %s' % (cat_headers(cd.headers), cd.legend)
            self._append_line(line)

    def iterlines(self):
        for line in self._lines:
            yield line

    def append_detail(self, **kwds):
        line = ''
        # detect and report extra arguments
        for k in kwds.keys():
            if k not in self._column_names:
                print 'Column %s is not defined in the ColumnTable' % k
                print 'defined column names are', self._column_names
                pdb.set_trace()
        # format values in kwds
        for cd in self._columns:
            if cd.name in kwds and kwds[cd.name] is not None:
                try:
                    glyph = cd.formatter % kwds[cd.name]
                except TypeError as e:
                    print '+++++++++++++++++++++++++'
                    print TypeError, e
                    print 'formatter  : %s' % cd.formatter
                    print 'field name : %s' % cd.name
                    print 'field value: %s' % kwds[cd.name]
                    print '+++++++++++++++++++++++++'
                    pdb.set_trace()
            else:
                # if missing or None, print spaces for the value
                glyph = ' ' * cd.width
            if len(line) > 0:
                line += ' '
            line += glyph
        self._append_line(line)

    def _append_line(self, line):
        if self._verbose:
            print line
        self._lines.append(line)

    def _header(self):
        def append_header(index):
            line = ''
            for cd in self._columns:
                formatter = '%' + str(cd.width) + 's'
                formatted = formatter % cd.headers[index]
                line += (' ' if len(line) > 0 else '') + formatted
            self._append_line(line)

        for header_line_index in xrange(self._number_of_header_lines):
            append_header(header_line_index)

    def _print(self):
        for line in self._lines:
            print line

class TestColumnsTable(unittest.TestCase):
    def setUp(self,):
        self.verbose = False
        self.columns = ColumnsTable(
            (('a', 3, '%3d', ('one', 'num'), 'angstroms'),
             ('bcd', 10, '%10.2f', ('length', 'meters'), 'something really big'),
             ),
            verbose=self.verbose,
        )

    def test_construction(self):
        self.assertTrue(True)
        pass  # tested in setUp()

    def test_append_detail_line(self):
        c = self.columns
        c.append_detail(a=10, bcd=20)
        c.append_detail(bcd=30)
        c.append_detail(a=50)
        self.assertEqual(len(c._lines), 5)
        self.assertTrue(True)

    def test_iterlines(self):
        c = self.columns
        counter = collections.Counter()

        for line in c.iterlines():
            counter['line'] += 1
            if self.verbose:
                print line

        self.assertEqual(counter['line'], 2)  # only 2 column headers lines

    def test_append_legend_lines(self):
        c = self.columns
        c.append_legend()
        self.assertTrue(True)

    def test_one_header(self):
        c = ColumnsTable(
            (('a', 10, '%d', 'first', 'words'),
             ('b', 20, '%d', 'second', 'more words'),
             )
        )
        self.assertEqual(c._number_of_header_lines, 1)

if __name__ == '__main__':
    unittest.main()
    if False:
        pdb
<|endoftext|>


<commit_before># coding=utf-8
# Copyright 2022 DeepMind Technologies Limited.
#
# 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.

"""Abstract class for handling a stream of logs from a simulator."""

import abc
import threading
from typing import Generator, List
from absl import logging

class LogStream(metaclass=abc.ABCMeta):
  """Manages the stream of logs output by a simulator."""

  def __init__(self, verbose: bool = False):
    self._verbose = verbose
    self._filters = []
    self._should_stream = threading.Event()

  def get_stream_output(self) -> Generator[str, None, None]:
    """Starts log process and returns the stream of logs."""
    for line in self._get_stream_output():
      if self._verbose:
        logging.info('line: %r', line)
      if self._should_stream.is_set():
        yield line

  @abc.abstractmethod
  def _get_stream_output(self):
    """Starts log process and returns the stream of logs."""
    pass

  @abc.abstractmethod
  def stop_stream(self) -> None:
    """Terminates the log stream.

    NOTE: This should only be called _after_ `get_stream_output()`.
    """

  def pause_stream(self) -> None:
    """No lines are yielded while the event is not set."""
    logging.info('Pausing LogStream.')
    self._should_stream.clear()

  def resume_stream(self) -> None:
    """The stream will continue yielding lines if the event is set."""
    logging.info('Resuming LogStream.')
    self._should_stream.set()

  def set_log_filters(self, log_filters: List[str]):
    """Sets the filters for the log stream."""
    self._filters = list(log_filters) + ['*:S']
<commit_msg>Fix typing error: `s/List/Sequence/`.<commit_after># coding=utf-8
# Copyright 2022 DeepMind Technologies Limited.
#
# 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.

"""Abstract class for handling a stream of logs from a simulator."""

import abc
import threading
from typing import Generator, Sequence
from absl import logging

class LogStream(metaclass=abc.ABCMeta):
  """Manages the stream of logs output by a simulator."""

  def __init__(self, verbose: bool = False):
    self._verbose = verbose
    self._filters = []
    self._should_stream = threading.Event()

  def get_stream_output(self) -> Generator[str, None, None]:
    """Starts log process and returns the stream of logs."""
    for line in self._get_stream_output():
      if self._verbose:
        logging.info('line: %r', line)
      if self._should_stream.is_set():
        yield line

  @abc.abstractmethod
  def _get_stream_output(self):
    """Starts log process and returns the stream of logs."""
    pass

  @abc.abstractmethod
  def stop_stream(self) -> None:
    """Terminates the log stream.

    NOTE: This should only be called _after_ `get_stream_output()`.
    """

  def pause_stream(self) -> None:
    """No lines are yielded while the event is not set."""
    logging.info('Pausing LogStream.')
    self._should_stream.clear()

  def resume_stream(self) -> None:
    """The stream will continue yielding lines if the event is set."""
    logging.info('Resuming LogStream.')
    self._should_stream.set()

  def set_log_filters(self, log_filters: Sequence[str]):
    """Sets the filters for the log stream."""
    self._filters = list(log_filters) + ['*:S']
<|endoftext|>