<commit_before>"""Classes to generate point and figure charts."""
from abc import ABCMeta
from abc import abstractmethod
from collections import OrderedDict
from datetime import datetime
from decimal import Decimal

import pypf.terminal_format

class PFChartBase(metaclass=ABCMeta):
    """Base class for point and figure charts."""

    TWOPLACES = Decimal('0.01')

    def __init__(self, period=1, box_size=.01, reversal=3, style_output=False):
        """Initialize common functionality."""
        self.period = period
        self.box_size = Decimal(box_size)
        self.reversal = int(reversal)
        self.style_output = style_output

        self.historical_data = []
        self.chart_data = []
        self.scale = OrderedDict()
        self.high_field = None
        self.low_field = None
        self.close_field = None
        self.date_field = None

        self.current_signal = None
        self.current_status = None
        self.current_action = None
        self.current_move = None
        self.current_column_index = None
        self.current_scale_index = None
        self.current_scale_value = None
        self.current_direction = None
        self.current_close = None

        self.chart_meta_data = OrderedDict()

    def create_chart(self, dump=False):
        """Populate the data and create the chart."""
        self._set_historical_data()
        self._set_price_fields()
        self._set_scale()
        self._set_chart_data()
        if dump:
            chart = self._get_chart()
            print(chart)

    def _get_chart(self):
        self._set_current_state()
        chart = ""
        chart += "\n"
        chart += self._get_chart_title()

        index = len(self.chart_data[0]) - 1
        first = True
        scale_right = None
        while index >= 0:
            for column in self.chart_data:
                if index in column:
                    if first:
                        scale_value = column[index]
                        if index == self.current_scale_index:
                            scale_left = (self._style('red',
                                          self._style('bold', '{:7.2f}'))
                                          .format(scale_value))
                            scale_right = (self._style('red',
                                           self._style('bold', '<< '))
                                           + self._style('red',
                                                         self._style('bold',
                                                                     '{:.2f}'))
                                           .format(self.current_close))
                        else:
                            scale_left = '{:7.2f}'.format(scale_value)
                            scale_right = '{:.2f}'.format(scale_value)
                        chart = chart + scale_left + '| '
                        first = False
                    else:
                        chart = chart + ' ' + column[index][0]
                else:
                    chart += '  '

            chart += '   |' + scale_right
            chart += "\n"
            index -= 1
            first = True
        return chart

    def _get_month(self, date_value):
        datetime_object = datetime.strptime(date_value, '%Y-%m-%d')
        month = str(datetime_object.month)
        if month == '10':
            month = 'A'
        elif month == '11':
            month = 'B'
        elif month == '12':
            month = 'C'
        return self._style('bold', self._style('red', month))

    def _get_scale_index(self, value, direction):
        index = 0
        while index < len(self.scale):
            if self.scale[index] == value:
                return index
            elif self.scale[index] > value:
                if direction == 'x':
                    return index - 1
                else:
                    return index
            index += 1

    def _set_chart_data(self):
        self.chart_data = []
        self.chart_meta_data = OrderedDict()
        self.chart_data.append(self.scale)

        column = OrderedDict()
        column_index = 1
        direction = 'x'
        index = None
        month = None
        signal = 'none'
        prior_high_index = len(self.scale) - 1
        prior_low_index = 0

        for row in self.historical_data:
            day = self.historical_data[row]
            action = 'none'
            move = 0
            current_month = self._get_month(day[self.date_field])

            if index is None:
                # First day - set the starting index based
                # on the high and 'x' direction
                index = self._get_scale_index(day[self.high_field], 'x')
                column[index] = ['x', day[self.date_field]]
                month = current_month
                continue

            if direction == 'x':
                scale_index = self._get_scale_index(day[self.high_field], 'x')

                if scale_index > index:
                    # new high
                    action = 'x'
                    move = scale_index - index

                    if signal != 'buy' and scale_index > prior_high_index:
                        signal = 'buy'

                    first = True
                    while index < scale_index:
                        index += 1
                        if first:
                            if current_month != month:
                                column[index] = [current_month,
                                                 day[self.date_field]]
                            else:
                                column[index] = ['x', day[self.date_field]]
                            first = False
                        else:
                            column[index] = ['x', day[self.date_field]]
                    month = current_month
                else:
                    # check for reversal
                    scale_index = self._get_scale_index(day[self.low_field],
                                                        'o')
                    if index - scale_index >= self.reversal:
                        # reversal
                        action = 'reverse x->o'
                        move = index - scale_index

                        if signal != 'sell' and scale_index < prior_low_index:
                            signal = 'sell'

                        prior_high_index = index
                        self.chart_data.append(column)
                        column_index += 1
                        column = OrderedDict()
                        direction = 'o'
                        first = True
                        while index > scale_index:
                            index -= 1
                            if first:
                                if current_month != month:
                                    column[index] = [current_month,
                                                     day[self.date_field]]
                                else:
                                    column[index] = ['d', day[self.date_field]]
                                first = False
                            else:
                                column[index] = ['d', day[self.date_field]]
                        month = current_month
                    else:
                        # no reversal - reset the scale_index
                        scale_index -= 1
            else:
                # in an 'o' column
                scale_index = self._get_scale_index(day[self.low_field], 'o')
                if scale_index < index:
                    # new low
                    action = 'o'
                    move = index - scale_index

                    if signal != 'sell' and scale_index < prior_low_index:
                        signal = 'sell'

                    first = True
                    while index > scale_index:
                        index -= 1
                        if first:
                            if current_month != month:
                                column[index] = [current_month,
                                                 day[self.date_field]]
                            else:
                                column[index] = ['o', day[self.date_field]]
                            first = False
                        else:
                            column[index] = ['o', day[self.date_field]]
                    month = current_month
                else:
                    # check for reversal
                    scale_index = self._get_scale_index(day[self.high_field],
                                                        'x')
                    if scale_index - index >= self.reversal:
                        # reversal
                        action = 'reverse o->x'
                        move = scale_index - index

                        if signal != 'buy' and scale_index > prior_high_index:
                            signal = 'buy'

                        prior_low_index = index
                        self.chart_data.append(column)
                        column_index += 1
                        column = OrderedDict()
                        direction = 'x'
                        first = True
                        while index < scale_index:
                            index += 1
                            if first:
                                if current_month != month:
                                    column[index] = [current_month,
                                                     day[self.date_field]]
                                else:
                                    column[index] = ['u', day[self.date_field]]
                                first = False
                            else:
                                column[index] = ['u', day[self.date_field]]
                        month = current_month
                    else:
                        # no reversal - reset the scale_index
                        scale_index += 1

            # Store the meta data for the day
            status = self._get_status(signal, direction)
            scale_value = (self.scale[scale_index]
                           .quantize(PFChartBase.TWOPLACES))
            prior_high = self.scale[prior_high_index]
            prior_low = self.scale[prior_low_index]
            self._store_base_metadata(day, signal, status, action, move,
                                      column_index, scale_index, scale_value,
                                      direction, prior_high, prior_low)

        self.chart_data.append(column)

        if len(self.chart_data[1]) < self.reversal:
            self.chart_data.pop(1)

        return self.chart_data

    def _set_current_state(self):
        current_meta_index = next(reversed(self.chart_meta_data))
        current_meta = self.chart_meta_data[current_meta_index]
        self.current_signal = current_meta['signal']
        self.current_status = current_meta['status']
        self.current_action = current_meta['action']
        self.current_move = current_meta['move']
        self.current_column_index = current_meta['column_index']
        self.current_scale_index = current_meta['scale_index']
        self.current_scale_value = current_meta['scale_value']
        self.current_direction = current_meta['direction']

    def _store_base_metadata(self, day, signal, status, action, move,
                             column_index, scale_index, scale_value,
                             direction, prior_high, prior_low):
        date_value = day['Date']
        self.chart_meta_data[date_value] = {}
        self.chart_meta_data[date_value]['signal'] = signal
        self.chart_meta_data[date_value]['status'] = status
        self.chart_meta_data[date_value]['action'] = action
        self.chart_meta_data[date_value]['move'] = move
        self.chart_meta_data[date_value]['column_index'] = column_index
        self.chart_meta_data[date_value]['scale_index'] = scale_index
        self.chart_meta_data[date_value]['scale_value'] = scale_value
        self.chart_meta_data[date_value]['direction'] = direction
        self.chart_meta_data[date_value]['prior_high'] = prior_high
        self.chart_meta_data[date_value]['prior_low'] = prior_low
        self._store_custom_metadata(day)

    def _style(self, style, message):
        if self.style_output:
            method = getattr(pypf.terminal_format, style)
            return method(message)
        else:
            return message

    @staticmethod
    def _get_status(signal, direction):
        if signal == 'buy' and direction == 'x':
            status = 'bull confirmed'
        elif signal == 'buy' and direction == 'o':
            status = 'bull correction'
        elif signal == 'sell' and direction == 'o':
            status = 'bear confirmed'
        elif signal == 'sell' and direction == 'x':
            status = 'bear correction'
        else:
            status = 'none'
        return status

    @abstractmethod
    def _get_chart_title(self):
        pass

    @abstractmethod
    def _set_historical_data(self):
        pass

    @abstractmethod
    def _set_price_fields(self):
        pass

    @abstractmethod
    def _set_scale(self):
        pass

    @abstractmethod
    def _store_custom_metadata(self, day):
        pass

class PFChart(PFChartBase):
    """Point and figure chart."""

    def __init__(self, security, period=1, box_size=.01, reversal=3,
                 method='HL', style_output=False):
        """Initialize point and figure chart."""
        super().__init__(period, box_size, reversal, style_output)
        self.security = security
        self.method = method
        self.current_date = ''
        self.current_open = ''
        self.current_high = ''
        self.current_low = ''
        self.current_close = ''

        self.open_field = None
        self.volume_field = None

    def _get_chart_title(self):
        self._set_current_prices()
        title = ""
        title = title + "  " + self._style('bold',
                                           self._style('underline',
                                                       self.security.symbol))
        title = (title
                 + "  ({:} o: {:.2f} h: {:.2f} l: {:.2f} c: {:.2f}"
                 .format(self.current_date, self.current_open,
                         self.current_high, self.current_low,
                         self.current_close)
                 + ")\n")

        title = (title
                 + "  "
                 + str((self.box_size * 100).quantize(PFChartBase.TWOPLACES))
                 + "% box, ")
        title = title + str(self.reversal) + " box reversal, "
        title = title + str(self.method) + " method\n"
        title = (title
                 + "  signal: "
                 + self._style('bold', self.current_signal)
                 + " status: " + self._style('bold', self.current_status)
                 + "\n\n")
        return title

    def _set_current_prices(self):
        day = next(reversed(self.historical_data))
        current_day = self.historical_data[day]
        self.current_date = current_day[self.date_field]
        self.current_open = (current_day[self.open_field]
                             .quantize(PFChartBase.TWOPLACES))
        self.current_high = (current_day[self.high_field]
                             .quantize(PFChartBase.TWOPLACES))
        self.current_low = (current_day[self.low_field]
                            .quantize(PFChartBase.TWOPLACES))
        self.current_close = (current_day[self.close_field]
                              .quantize(PFChartBase.TWOPLACES))

    def _set_historical_data(self):
        if len(self.security.historical_data) == 0:
            self.security.populate_data()

        days = int(self.period * 252)
        if len(self.security.historical_data) > days:
            offset = len(self.security.historical_data) - days
            i = 0
            while i < offset:
                self.security.historical_data.popitem(False)
                i += 1
            self.historical_data = self.security.historical_data
        else:
            self.historical_data = self.security.historical_data

    def _set_price_fields(self):
        if self.method == 'HL':
            self.high_field = 'High'
            self.low_field = 'Low'
        else:
            self.high_field = 'Close'
            self.low_field = 'Close'
        self.open_field = 'Open'
        self.close_field = 'Close'
        self.volume_field = 'Volume'
        self.date_field = 'Date'

    def _set_scale(self):
        row = next(iter(self.historical_data))
        day = self.historical_data[row]
        highest = day[self.high_field]
        lowest = day[self.low_field]

        for row in self.historical_data:
            day = self.historical_data[row]
            if day[self.high_field] > highest:
                highest = day[self.high_field]
            if day[self.low_field] < lowest:
                lowest = day[self.low_field]

        temp_scale = []
        current = Decimal(.01)
        temp_scale.append(current)

        while current < highest:
            value = current + (current * self.box_size)
            temp_scale.append(value)
            current = value

        slice_point = 0
        for index, scale_value in enumerate(temp_scale):
            if scale_value > lowest:
                slice_point = index - 1
                break
        temp_scale = temp_scale[slice_point:]

        self.scale = OrderedDict()
        for index, scale_value in enumerate(temp_scale):
            self.scale[index] = scale_value

    def _store_custom_metadata(self, date_value):
        pass
<commit_msg>Simplified by refactoring away unneeded class hierarchy.<commit_after>"""Classes to generate point and figure charts."""
from collections import OrderedDict
from datetime import datetime
from decimal import Decimal

import pypf.terminal_format

class PFChart(object):
    """Base class for point and figure charts."""

    TWOPLACES = Decimal('0.01')

    def __init__(self, security, period=1, box_size=.01, reversal=3,
                 method='HL', style_output=False):
        """Initialize common functionality."""
        self.security = security
        self.method = method
        self.period = period
        self.box_size = Decimal(box_size)
        self.reversal = int(reversal)
        self.style_output = style_output

        self.historical_data = []
        self.chart_data = []
        self.scale = OrderedDict()

        self.current_date = ''
        self.current_open = ''
        self.current_high = ''
        self.current_low = ''
        self.current_close = ''

        self.open_field = None
        self.high_field = None
        self.low_field = None
        self.close_field = None
        self.volume_field = None
        self.date_field = None

        self.current_signal = None
        self.current_status = None
        self.current_action = None
        self.current_move = None
        self.current_column_index = None
        self.current_scale_index = None
        self.current_scale_value = None
        self.current_direction = None
        self.current_close = None

        self.chart_meta_data = OrderedDict()

    def create_chart(self, dump=False):
        """Populate the data and create the chart."""
        self._set_historical_data()
        self._set_price_fields()
        self._set_scale()
        self._set_chart_data()
        if dump:
            chart = self._get_chart()
            print(chart)

    def _get_chart(self):
        self._set_current_state()
        chart = ""
        chart += "\n"
        chart += self._get_chart_title()

        index = len(self.chart_data[0]) - 1
        first = True
        scale_right = None
        while index >= 0:
            for column in self.chart_data:
                if index in column:
                    if first:
                        scale_value = column[index]
                        if index == self.current_scale_index:
                            scale_left = (self._style('red',
                                          self._style('bold', '{:7.2f}'))
                                          .format(scale_value))
                            scale_right = (self._style('red',
                                           self._style('bold', '<< '))
                                           + self._style('red',
                                                         self._style('bold',
                                                                     '{:.2f}'))
                                           .format(self.current_close))
                        else:
                            scale_left = '{:7.2f}'.format(scale_value)
                            scale_right = '{:.2f}'.format(scale_value)
                        chart = chart + scale_left + '| '
                        first = False
                    else:
                        chart = chart + ' ' + column[index][0]
                else:
                    chart += '  '

            chart += '   |' + scale_right
            chart += "\n"
            index -= 1
            first = True
        return chart

    def _get_month(self, date_value):
        datetime_object = datetime.strptime(date_value, '%Y-%m-%d')
        month = str(datetime_object.month)
        if month == '10':
            month = 'A'
        elif month == '11':
            month = 'B'
        elif month == '12':
            month = 'C'
        return self._style('bold', self._style('red', month))

    def _get_scale_index(self, value, direction):
        index = 0
        while index < len(self.scale):
            if self.scale[index] == value:
                return index
            elif self.scale[index] > value:
                if direction == 'x':
                    return index - 1
                else:
                    return index
            index += 1

    def _get_status(self, signal, direction):
        if signal == 'buy' and direction == 'x':
            status = 'bull confirmed'
        elif signal == 'buy' and direction == 'o':
            status = 'bull correction'
        elif signal == 'sell' and direction == 'o':
            status = 'bear confirmed'
        elif signal == 'sell' and direction == 'x':
            status = 'bear correction'
        else:
            status = 'none'
        return status

    def _set_chart_data(self):
        self.chart_data = []
        self.chart_meta_data = OrderedDict()
        self.chart_data.append(self.scale)

        column = OrderedDict()
        column_index = 1
        direction = 'x'
        index = None
        month = None
        signal = 'none'
        prior_high_index = len(self.scale) - 1
        prior_low_index = 0

        for row in self.historical_data:
            day = self.historical_data[row]
            action = 'none'
            move = 0
            current_month = self._get_month(day[self.date_field])

            if index is None:
                # First day - set the starting index based
                # on the high and 'x' direction
                index = self._get_scale_index(day[self.high_field], 'x')
                column[index] = ['x', day[self.date_field]]
                month = current_month
                continue

            if direction == 'x':
                scale_index = self._get_scale_index(day[self.high_field], 'x')

                if scale_index > index:
                    # new high
                    action = 'x'
                    move = scale_index - index

                    if signal != 'buy' and scale_index > prior_high_index:
                        signal = 'buy'

                    first = True
                    while index < scale_index:
                        index += 1
                        if first:
                            if current_month != month:
                                column[index] = [current_month,
                                                 day[self.date_field]]
                            else:
                                column[index] = ['x', day[self.date_field]]
                            first = False
                        else:
                            column[index] = ['x', day[self.date_field]]
                    month = current_month
                else:
                    # check for reversal
                    scale_index = self._get_scale_index(day[self.low_field],
                                                        'o')
                    if index - scale_index >= self.reversal:
                        # reversal
                        action = 'reverse x->o'
                        move = index - scale_index

                        if signal != 'sell' and scale_index < prior_low_index:
                            signal = 'sell'

                        prior_high_index = index
                        self.chart_data.append(column)
                        column_index += 1
                        column = OrderedDict()
                        direction = 'o'
                        first = True
                        while index > scale_index:
                            index -= 1
                            if first:
                                if current_month != month:
                                    column[index] = [current_month,
                                                     day[self.date_field]]
                                else:
                                    column[index] = ['d', day[self.date_field]]
                                first = False
                            else:
                                column[index] = ['d', day[self.date_field]]
                        month = current_month
                    else:
                        # no reversal - reset the scale_index
                        scale_index -= 1
            else:
                # in an 'o' column
                scale_index = self._get_scale_index(day[self.low_field], 'o')
                if scale_index < index:
                    # new low
                    action = 'o'
                    move = index - scale_index

                    if signal != 'sell' and scale_index < prior_low_index:
                        signal = 'sell'

                    first = True
                    while index > scale_index:
                        index -= 1
                        if first:
                            if current_month != month:
                                column[index] = [current_month,
                                                 day[self.date_field]]
                            else:
                                column[index] = ['o', day[self.date_field]]
                            first = False
                        else:
                            column[index] = ['o', day[self.date_field]]
                    month = current_month
                else:
                    # check for reversal
                    scale_index = self._get_scale_index(day[self.high_field],
                                                        'x')
                    if scale_index - index >= self.reversal:
                        # reversal
                        action = 'reverse o->x'
                        move = scale_index - index

                        if signal != 'buy' and scale_index > prior_high_index:
                            signal = 'buy'

                        prior_low_index = index
                        self.chart_data.append(column)
                        column_index += 1
                        column = OrderedDict()
                        direction = 'x'
                        first = True
                        while index < scale_index:
                            index += 1
                            if first:
                                if current_month != month:
                                    column[index] = [current_month,
                                                     day[self.date_field]]
                                else:
                                    column[index] = ['u', day[self.date_field]]
                                first = False
                            else:
                                column[index] = ['u', day[self.date_field]]
                        month = current_month
                    else:
                        # no reversal - reset the scale_index
                        scale_index += 1

            # Store the meta data for the day
            status = self._get_status(signal, direction)
            scale_value = (self.scale[scale_index]
                           .quantize(PFChart.TWOPLACES))
            prior_high = self.scale[prior_high_index]
            prior_low = self.scale[prior_low_index]
            self._store_base_metadata(day, signal, status, action, move,
                                      column_index, scale_index, scale_value,
                                      direction, prior_high, prior_low)

        self.chart_data.append(column)

        if len(self.chart_data[1]) < self.reversal:
            self.chart_data.pop(1)

        return self.chart_data

    def _set_current_state(self):
        current_meta_index = next(reversed(self.chart_meta_data))
        current_meta = self.chart_meta_data[current_meta_index]
        self.current_signal = current_meta['signal']
        self.current_status = current_meta['status']
        self.current_action = current_meta['action']
        self.current_move = current_meta['move']
        self.current_column_index = current_meta['column_index']
        self.current_scale_index = current_meta['scale_index']
        self.current_scale_value = current_meta['scale_value']
        self.current_direction = current_meta['direction']

    def _store_base_metadata(self, day, signal, status, action, move,
                             column_index, scale_index, scale_value,
                             direction, prior_high, prior_low):
        date_value = day['Date']
        self.chart_meta_data[date_value] = {}
        self.chart_meta_data[date_value]['signal'] = signal
        self.chart_meta_data[date_value]['status'] = status
        self.chart_meta_data[date_value]['action'] = action
        self.chart_meta_data[date_value]['move'] = move
        self.chart_meta_data[date_value]['column_index'] = column_index
        self.chart_meta_data[date_value]['scale_index'] = scale_index
        self.chart_meta_data[date_value]['scale_value'] = scale_value
        self.chart_meta_data[date_value]['direction'] = direction
        self.chart_meta_data[date_value]['prior_high'] = prior_high
        self.chart_meta_data[date_value]['prior_low'] = prior_low
        self._store_custom_metadata(day)

    def _style(self, style, message):
        if self.style_output:
            method = getattr(pypf.terminal_format, style)
            return method(message)
        else:
            return message

    def _get_chart_title(self):
        self._set_current_prices()
        title = ""
        title = title + "  " + self._style('bold',
                                           self._style('underline',
                                                       self.security.symbol))
        title = (title
                 + "  ({:} o: {:.2f} h: {:.2f} l: {:.2f} c: {:.2f}"
                 .format(self.current_date, self.current_open,
                         self.current_high, self.current_low,
                         self.current_close)
                 + ")\n")

        title = (title
                 + "  "
                 + str((self.box_size * 100).quantize(PFChart.TWOPLACES))
                 + "% box, ")
        title = title + str(self.reversal) + " box reversal, "
        title = title + str(self.method) + " method\n"
        title = (title
                 + "  signal: "
                 + self._style('bold', self.current_signal)
                 + " status: " + self._style('bold', self.current_status)
                 + "\n\n")
        return title

    def _set_current_prices(self):
        day = next(reversed(self.historical_data))
        current_day = self.historical_data[day]
        self.current_date = current_day[self.date_field]
        self.current_open = (current_day[self.open_field]
                             .quantize(PFChart.TWOPLACES))
        self.current_high = (current_day[self.high_field]
                             .quantize(PFChart.TWOPLACES))
        self.current_low = (current_day[self.low_field]
                            .quantize(PFChart.TWOPLACES))
        self.current_close = (current_day[self.close_field]
                              .quantize(PFChart.TWOPLACES))

    def _set_historical_data(self):
        if len(self.security.historical_data) == 0:
            self.security.populate_data()

        days = int(self.period * 252)
        if len(self.security.historical_data) > days:
            offset = len(self.security.historical_data) - days
            i = 0
            while i < offset:
                self.security.historical_data.popitem(False)
                i += 1
            self.historical_data = self.security.historical_data
        else:
            self.historical_data = self.security.historical_data

    def _set_price_fields(self):
        if self.method == 'HL':
            self.high_field = 'High'
            self.low_field = 'Low'
        else:
            self.high_field = 'Close'
            self.low_field = 'Close'
        self.open_field = 'Open'
        self.close_field = 'Close'
        self.volume_field = 'Volume'
        self.date_field = 'Date'

    def _set_scale(self):
        row = next(iter(self.historical_data))
        day = self.historical_data[row]
        highest = day[self.high_field]
        lowest = day[self.low_field]

        for row in self.historical_data:
            day = self.historical_data[row]
            if day[self.high_field] > highest:
                highest = day[self.high_field]
            if day[self.low_field] < lowest:
                lowest = day[self.low_field]

        temp_scale = []
        current = Decimal(.01)
        temp_scale.append(current)

        while current < highest:
            value = current + (current * self.box_size)
            temp_scale.append(value)
            current = value

        slice_point = 0
        for index, scale_value in enumerate(temp_scale):
            if scale_value > lowest:
                slice_point = index - 1
                break
        temp_scale = temp_scale[slice_point:]

        self.scale = OrderedDict()
        for index, scale_value in enumerate(temp_scale):
            self.scale[index] = scale_value

    def _store_custom_metadata(self, day):
        pass
<|endoftext|>


<commit_before>'''

This file contains classes and functions that implement the PyPXE TFTP service

'''

import socket
import struct
import os
import select
import time
import logging
import math
from pypxe import helpers

class ParentSocket(socket.socket):
    '''Subclassed socket.socket to enable a link-back to the client object.'''
    parent = None

class Client:
    '''Client instance for TFTPD.'''
    def __init__(self, mainsock, parent):
        self.default_retries = parent.default_retries
        self.timeout = parent.timeout
        self.ip = parent.ip
        self.message, self.address = mainsock.recvfrom(1024)
        self.logger = helpers.get_child_logger(parent.logger, 'Client.{0}'.format(self.address))
        self.netboot_directory = parent.netboot_directory
        self.logger.debug('Receiving request...')
        self.retries = self.default_retries
        self.block = 1
        self.blksize = 512
        self.sent_time = float('inf')
        self.dead = False
        self.fh = None
        self.filename = ''
        self.wrap = 0
        self.arm_wrap = False
        self.handle() # message from the main socket

    def ready(self):
        '''Called when there is something to be read on our socket.'''
        self.message = self.sock.recv(1024)
        self.handle()

    def send_block(self):
        '''
            Sends the next block of data, setting the timeout and retry
            variables accordingly.
        '''
        data = None
        try:
            self.fh.seek(self.blksize * (self.block - 1))
            data = self.fh.read(self.blksize)
        except:
            self.logger.error('Error while reading block {0}'.format(self.block))
            self.dead = True
            return
        # opcode 3 == DATA, wraparound block number
        response = struct.pack('!HH', 3, self.block % 65536)
        response += data
        self.sock.sendto(response, self.address)
        self.logger.debug('Sending block {0}'.format(self.block))
        self.retries -= 1
        self.sent_time = time.time()

    def no_ack(self):
        '''Determines if we timed out waiting for an ACK from the client.'''
        if self.sent_time + self.timeout < time.time():
            return True
        return False

    def no_retries(self):
        '''Determines if the client ran out of retry attempts.'''
        if not self.retries:
            return True
        return False

    def valid_mode(self):
        '''Determines if the file read mode octet; if not, send an error.'''
        mode = self.message.split(b'\x00')[1]
        if mode == b'octet': return True
        self.send_error(5, 'Mode {0} not supported'.format(mode))
        return False

    def check_file(self):
        '''
            Determines if the file exists under the netboot_directory,
            and if it is a file; if not, send an error.
        '''
        filename = self.message.split(b'\x00')[0].decode('ascii').lstrip('/')
        try:
            filename = helpers.normalize_path(self.netboot_directory, filename)
        except helpers.PathTraversalException:
            self.send_error(2, 'Path traversal error', filename = filename)
            return False
        if os.path.lexists(filename) and os.path.isfile(filename):
            self.filename = filename
            return True
        self.send_error(1, 'File Not Found', filename = filename)
        return False

    def parse_options(self):
        '''
            Extracts the options sent from a client; if any, calculates the last
            block based on the filesize and blocksize.
        '''
        options = self.message.split(b'\x00')[2: -1]
        options = dict(zip((i.decode('ascii') for i in options[0::2]), map(int, options[1::2])))
        self.changed_blksize = 'blksize' in options
        if self.changed_blksize:
            self.blksize = options['blksize']
        self.lastblock = math.ceil(self.filesize / float(self.blksize))
        self.tsize = True if 'tsize' in options else False
        if self.filesize > (2 ** 16) * self.blksize:
            self.logger.warning('Request too big, attempting transfer anyway.')
            self.logger.debug('Details: Filesize {0} is too big for blksize {1}.'.format(self.filesize, self.blksize))
        if len(options):
            # we need to know later if we actually had any options
            self.block = 0
            return True
        else:
            return False

    def reply_options(self):
        '''Acknowledges any options received.'''
        # only called if options, so send them all
        response = struct.pack("!H", 6)
        if self.changed_blksize:
            response += b'blksize' + b'\x00'
            response += str(self.blksize) + b'\x00'
        if self.tsize:
            response += b'tsize' + b'\x00'
            response += str(self.filesize).encode('ascii') + b'\x00'
        self.sock.sendto(response, self.address)

    def new_request(self):
        '''
            When receiving a read request from the parent socket, open our
            own socket and check the read request; if we don't have any options,
            send the first block.
        '''
        self.sock = ParentSocket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.ip, 0))
        # used by select() to find ready clients
        self.sock.parent = self
        if not self.valid_mode() or not self.check_file():
            # some clients just ACK the error (wrong code?)
            # so forcefully shutdown
            self.complete()
            return
        self.fh = open(self.filename, 'rb')
        self.filesize = os.path.getsize(self.filename)
        if not self.parse_options():
            # no options received so start transfer
            if self.block == 1:
                self.send_block()
            return
        self.reply_options() # we received some options so ACK those first

    def send_error(self, code = 1, message = 'File Not Found', filename = ''):
        '''
            Sends an error code and string to a client. See RFC1350, page 10 for
            details.

            Value   Meaning
            =====   =======
            0       Not defined, see error message (if any).
            1       File not found.
            2       Access violation.
            3       Disk full or allocation exceeded.
            4       Illegal TFTP operation.
            5       Unknown transfer ID.
            6       File already exists.
            7       No such user.
        '''
        response =  struct.pack('!H', 5) # error opcode
        response += struct.pack('!H', code) # error code
        response += message.encode('ascii')
        response += b'\x00'
        self.sock.sendto(response, self.address)
        self.logger.info('Sending {0}: {1} {2}'.format(code, message, filename))

    def complete(self):
        '''
            Closes a file and socket after sending it
            and marks ourselves as dead to be cleaned up.
        '''
        try:
            self.fh.close()
        except AttributeError:
            pass # we have not opened yet or file-not-found
        self.sock.close()
        self.dead = True

    def handle(self):
        '''Takes the message from the parent socket and act accordingly.'''
        # if addr not in ongoing, call this, else ready()
        [opcode] = struct.unpack('!H', self.message[:2])
        if opcode == 1:
            self.message = self.message[2:]
            self.new_request()
        elif opcode == 4:
            [block] = struct.unpack('!H', self.message[2:4])
            if block == 0 and self.arm_wrap:
                self.wrap += 1
                self.arm_wrap = False
            if block == 32768:
                self.arm_wrap = True
            if block < self.block % 65536:
                self.logger.warning('Ignoring duplicated ACK received for block {0}'.format(self.block))
            elif block > self.block % 65536:
                self.logger.warning('Ignoring out of sequence ACK received for block {0}'.format(self.block))
            elif block + self.wrap * 65536 == self.lastblock:
                if self.filesize % self.blksize == 0:
                    self.block = block + 1
                    self.send_block()
                self.logger.info('Completed sending {0}'.format(self.filename))
                self.complete()
            else:
                self.block = block + 1
                self.retries = self.default_retries
                self.send_block()
        elif opcode == 2:
            # write request
            self.sock = ParentSocket(socket.AF_INET, socket.SOCK_DGRAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind((self.ip, 0))
            # used by select() to find ready clients
            self.sock.parent = self
            # send error
            self.send_error(4, 'Write support not implemented')
            self.dead = True

class TFTPD:
    '''
        This class implements a read-only TFTP server
        implemented from RFC1350 and RFC2348
    '''
    def __init__(self, **server_settings):
        self.ip = server_settings.get('ip', '0.0.0.0')
        self.port = int(server_settings.get('port', 69))
        self.netboot_directory = server_settings.get('netboot_directory', '.')
        self.mode_verbose = server_settings.get('mode_verbose', False) # verbose mode
        self.mode_debug = server_settings.get('mode_debug', False) # debug mode
        self.logger = server_settings.get('logger', None)
        self.default_retries = server_settings.get('default_retries', 3)
        self.timeout = server_settings.get('timeout', 5)
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.ip, self.port))

        # setup logger
        if self.logger == None:
            self.logger = logging.getLogger('TFTP')
            handler = logging.StreamHandler()
            formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)

        if self.mode_debug:
            self.logger.setLevel(logging.DEBUG)
        elif self.mode_verbose:
            self.logger.setLevel(logging.INFO)
        else:
            self.logger.setLevel(logging.WARN)

        self.logger.debug('NOTICE: TFTP server started in debug mode. TFTP server is using the following:')
        self.logger.debug('Server IP: {0}'.format(self.ip))
        self.logger.debug('Server Port: {0}'.format(self.port))
        self.logger.debug('Network Boot Directory: {0}'.format(self.netboot_directory))

        self.ongoing = []

    def listen(self):
        '''This method listens for incoming requests.'''
        while True:
            # remove complete clients to select doesn't fail
            for client in self.ongoing:
                if client.dead:
                    self.ongoing.remove(client)
            rlist, _, _ = select.select([self.sock] + [client.sock for client in self.ongoing if not client.dead], [], [], 1)
            for sock in rlist:
                if sock == self.sock:
                    # main socket, so new client
                    self.ongoing.append(Client(sock, self))
                else:
                    # client socket, so tell the client object it's ready
                    sock.parent.ready()
            # if we haven't received an ACK in timeout time, retry
            [client.send_block() for client in self.ongoing if client.no_ack()]
            # if we have run out of retries, kill the client
            [client.complete() for client in self.ongoing if client.no_retries()]
<commit_msg>Notify file transfer progress and timeouts<commit_after>'''

This file contains classes and functions that implement the PyPXE TFTP service

'''

import socket
import struct
import os
import select
import time
import logging
import math
from pypxe import helpers

class ParentSocket(socket.socket):
    '''Subclassed socket.socket to enable a link-back to the client object.'''
    parent = None

class Client:
    '''Client instance for TFTPD.'''
    def __init__(self, mainsock, parent):
        self.default_retries = parent.default_retries
        self.timeout = parent.timeout
        self.ip = parent.ip
        self.message, self.address = mainsock.recvfrom(1024)
        self.logger = helpers.get_child_logger(parent.logger, 'Client.{0}'.format(self.address))
        self.netboot_directory = parent.netboot_directory
        self.logger.debug('Receiving request...')
        self.retries = self.default_retries
        self.block = 1
        self.blksize = 512
        self.sent_time = float('inf')
        self.dead = False
        self.fh = None
        self.filename = ''
        self.wrap = 0
        self.arm_wrap = False
        self.handle() # message from the main socket

    def ready(self):
        '''Called when there is something to be read on our socket.'''
        self.message = self.sock.recv(1024)
        self.handle()

    def send_block(self):
        '''
            Sends the next block of data, setting the timeout and retry
            variables accordingly.
        '''
        data = None
        try:
            self.fh.seek(self.blksize * (self.block - 1))
            data = self.fh.read(self.blksize)
        except:
            self.logger.error('Error while reading block {0}'.format(self.block))
            self.dead = True
            return
        # opcode 3 == DATA, wraparound block number
        response = struct.pack('!HH', 3, self.block % 65536)
        response += data
        self.sock.sendto(response, self.address)
        self.logger.debug('Sending block {0}/{1}'.format(self.block, self.lastblock))
        self.retries -= 1
        self.sent_time = time.time()

    def no_ack(self):
        '''Determines if we timed out waiting for an ACK from the client.'''
        if self.sent_time + self.timeout < time.time():
            return True
        return False

    def no_retries(self):
        '''Determines if the client ran out of retry attempts.'''
        if not self.retries:
            return True
        return False

    def valid_mode(self):
        '''Determines if the file read mode octet; if not, send an error.'''
        mode = self.message.split(b'\x00')[1]
        if mode == b'octet': return True
        self.send_error(5, 'Mode {0} not supported'.format(mode))
        return False

    def check_file(self):
        '''
            Determines if the file exists under the netboot_directory,
            and if it is a file; if not, send an error.
        '''
        filename = self.message.split(b'\x00')[0].decode('ascii').lstrip('/')
        try:
            filename = helpers.normalize_path(self.netboot_directory, filename)
        except helpers.PathTraversalException:
            self.send_error(2, 'Path traversal error', filename = filename)
            return False
        if os.path.lexists(filename) and os.path.isfile(filename):
            self.filename = filename
            return True
        self.send_error(1, 'File Not Found', filename = filename)
        return False

    def parse_options(self):
        '''
            Extracts the options sent from a client; if any, calculates the last
            block based on the filesize and blocksize.
        '''
        options = self.message.split(b'\x00')[2: -1]
        options = dict(zip((i.decode('ascii') for i in options[0::2]), map(int, options[1::2])))
        self.changed_blksize = 'blksize' in options
        if self.changed_blksize:
            self.blksize = options['blksize']
        self.lastblock = math.ceil(self.filesize / float(self.blksize))
        self.tsize = True if 'tsize' in options else False
        if self.filesize > (2 ** 16) * self.blksize:
            self.logger.warning('Request too big, attempting transfer anyway.')
            self.logger.debug('Details: Filesize {0} is too big for blksize {1}.'.format(self.filesize, self.blksize))
        if len(options):
            # we need to know later if we actually had any options
            self.block = 0
            return True
        else:
            return False

    def reply_options(self):
        '''Acknowledges any options received.'''
        # only called if options, so send them all
        response = struct.pack("!H", 6)
        if self.changed_blksize:
            response += b'blksize' + b'\x00'
            response += str(self.blksize) + b'\x00'
        if self.tsize:
            response += b'tsize' + b'\x00'
            response += str(self.filesize).encode('ascii') + b'\x00'
        self.sock.sendto(response, self.address)

    def new_request(self):
        '''
            When receiving a read request from the parent socket, open our
            own socket and check the read request; if we don't have any options,
            send the first block.
        '''
        self.sock = ParentSocket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.ip, 0))
        # used by select() to find ready clients
        self.sock.parent = self
        if not self.valid_mode() or not self.check_file():
            # some clients just ACK the error (wrong code?)
            # so forcefully shutdown
            self.complete()
            return
        self.fh = open(self.filename, 'rb')
        self.filesize = os.path.getsize(self.filename)
        if not self.parse_options():
            # no options received so start transfer
            if self.block == 1:
                self.send_block()
            return
        self.reply_options() # we received some options so ACK those first

    def send_error(self, code = 1, message = 'File Not Found', filename = ''):
        '''
            Sends an error code and string to a client. See RFC1350, page 10 for
            details.

            Value   Meaning
            =====   =======
            0       Not defined, see error message (if any).
            1       File not found.
            2       Access violation.
            3       Disk full or allocation exceeded.
            4       Illegal TFTP operation.
            5       Unknown transfer ID.
            6       File already exists.
            7       No such user.
        '''
        response =  struct.pack('!H', 5) # error opcode
        response += struct.pack('!H', code) # error code
        response += message.encode('ascii')
        response += b'\x00'
        self.sock.sendto(response, self.address)
        self.logger.info('Sending {0}: {1} {2}'.format(code, message, filename))

    def complete(self):
        '''
            Closes a file and socket after sending it
            and marks ourselves as dead to be cleaned up.
        '''
        try:
            self.fh.close()
        except AttributeError:
            pass # we have not opened yet or file-not-found
        self.sock.close()
        self.dead = True

    def handle(self):
        '''Takes the message from the parent socket and act accordingly.'''
        # if addr not in ongoing, call this, else ready()
        [opcode] = struct.unpack('!H', self.message[:2])
        if opcode == 1:
            self.message = self.message[2:]
            self.new_request()
        elif opcode == 4:
            [block] = struct.unpack('!H', self.message[2:4])
            if block == 0 and self.arm_wrap:
                self.wrap += 1
                self.arm_wrap = False
            if block == 32768:
                self.arm_wrap = True
            if block < self.block % 65536:
                self.logger.warning('Ignoring duplicated ACK received for block {0}'.format(self.block))
            elif block > self.block % 65536:
                self.logger.warning('Ignoring out of sequence ACK received for block {0}'.format(self.block))
            elif block + self.wrap * 65536 == self.lastblock:
                if self.filesize % self.blksize == 0:
                    self.block = block + 1
                    self.send_block()
                self.logger.info('Completed sending {0}'.format(self.filename))
                self.complete()
            else:
                self.block = block + 1
                self.retries = self.default_retries
                self.send_block()
        elif opcode == 2:
            # write request
            self.sock = ParentSocket(socket.AF_INET, socket.SOCK_DGRAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind((self.ip, 0))
            # used by select() to find ready clients
            self.sock.parent = self
            # send error
            self.send_error(4, 'Write support not implemented')
            self.dead = True

class TFTPD:
    '''
        This class implements a read-only TFTP server
        implemented from RFC1350 and RFC2348
    '''
    def __init__(self, **server_settings):
        self.ip = server_settings.get('ip', '0.0.0.0')
        self.port = int(server_settings.get('port', 69))
        self.netboot_directory = server_settings.get('netboot_directory', '.')
        self.mode_verbose = server_settings.get('mode_verbose', False) # verbose mode
        self.mode_debug = server_settings.get('mode_debug', False) # debug mode
        self.logger = server_settings.get('logger', None)
        self.default_retries = server_settings.get('default_retries', 3)
        self.timeout = server_settings.get('timeout', 5)
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.ip, self.port))

        # setup logger
        if self.logger == None:
            self.logger = logging.getLogger('TFTP')
            handler = logging.StreamHandler()
            formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)

        if self.mode_debug:
            self.logger.setLevel(logging.DEBUG)
        elif self.mode_verbose:
            self.logger.setLevel(logging.INFO)
        else:
            self.logger.setLevel(logging.WARN)

        self.logger.debug('NOTICE: TFTP server started in debug mode. TFTP server is using the following:')
        self.logger.debug('Server IP: {0}'.format(self.ip))
        self.logger.debug('Server Port: {0}'.format(self.port))
        self.logger.debug('Network Boot Directory: {0}'.format(self.netboot_directory))

        self.ongoing = []

    def listen(self):
        '''This method listens for incoming requests.'''
        while True:
            # remove complete clients to select doesn't fail
            for client in self.ongoing:
                if client.dead:
                    self.ongoing.remove(client)
            rlist, _, _ = select.select([self.sock] + [client.sock for client in self.ongoing if not client.dead], [], [], 1)
            for sock in rlist:
                if sock == self.sock:
                    # main socket, so new client
                    self.ongoing.append(Client(sock, self))
                else:
                    # client socket, so tell the client object it's ready
                    sock.parent.ready()
            # if we haven't received an ACK in timeout time, retry
            [client.send_block() for client in self.ongoing if client.no_ack()]
            # if we have run out of retries, kill the client
            for client in self.ongoing:
                if client.no_retries():
                    self.logger.info('Timeout while sending {0}'.format(self.filename))
                    client.complete()
<|endoftext|>


<commit_before>import logging
from pyrate import loader
import argparse

def main():
    """ The command line interface

    Type `pyrate --help` for help on how to use the command line interface
    """
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    # load tool components
    config = loader.DEFAULT_CONFIG
    config.read(['aistool.conf'])
    l = loader.Loader(config)

    def list_components(args):
        print("{} repositories:".format(len(l.get_data_repositories())))
        for r in l.get_data_repositories():
            print("\t"+r)

        print("{} algorithms:".format(len(l.get_algorithms())))

        for a in l.get_algorithms():
            print("\t"+a)

    def execute_repo_command(args):
        l.execute_repository_command(args.repo, args.cmd)

    def execute_algorithm(args):
        l.execute_algorithm_command(args.alg, args.cmd)

    # set up command line parser
    parser = argparse.ArgumentParser(description="AIS Super tool")
    subparsers = parser.add_subparsers(help='available commands')

    parser_list = subparsers.add_parser('list', help='list loaded data repositories and algorithms')
    parser_list.set_defaults(func=list_components)

    for r in l.get_data_repositories():
        repo_parser = subparsers.add_parser(r, help='commands for '+ r +' repository')
        repo_subparser = repo_parser.add_subparsers(help=r+' repository commands.')
        for cmd, desc in l.get_repository_commands(r):
            cmd_parser = repo_subparser.add_parser(cmd, help=desc)
            cmd_parser.set_defaults(func=execute_repo_command, cmd=cmd, repo=r)

    for a in l.get_algorithms():
        alg_parser = subparsers.add_parser(a, help='commands for algorithm '+ a +'')
        alg_subparser = alg_parser.add_subparsers(help=a+' algorithm commands.')
        for cmd, desc in l.get_algorithm_commands(a):
            alg_parser = alg_subparser.add_parser(cmd, help=desc)
            alg_parser.set_defaults(func=execute_algorithm, cmd=cmd, alg=a)

    args = parser.parse_args()
    if 'func' in args:
        args.func(args)
    else:
        parser.print_help()

if __name__ == '__main__':
    main()
<commit_msg>Update cli.py<commit_after>"""Provides a command line interface to the pyrate library
"""
import logging
from pyrate import loader
import argparse

def main():
    """ The command line interface

    Type `pyrate --help` for help on how to use the command line interface
    """
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    # load tool components
    config = loader.DEFAULT_CONFIG
    config.read(['aistool.conf'])
    l = loader.Loader(config)

    def list_components(args):
        print("{} repositories:".format(len(l.get_data_repositories())))
        for r in l.get_data_repositories():
            print("\t"+r)

        print("{} algorithms:".format(len(l.get_algorithms())))

        for a in l.get_algorithms():
            print("\t"+a)

    def execute_repo_command(args):
        l.execute_repository_command(args.repo, args.cmd)

    def execute_algorithm(args):
        l.execute_algorithm_command(args.alg, args.cmd)

    # set up command line parser
    parser = argparse.ArgumentParser(description="AIS Super tool")
    subparsers = parser.add_subparsers(help='available commands')

    parser_list = subparsers.add_parser('list', help='list loaded data repositories and algorithms')
    parser_list.set_defaults(func=list_components)

    for r in l.get_data_repositories():
        repo_parser = subparsers.add_parser(r, help='commands for '+ r +' repository')
        repo_subparser = repo_parser.add_subparsers(help=r+' repository commands.')
        for cmd, desc in l.get_repository_commands(r):
            cmd_parser = repo_subparser.add_parser(cmd, help=desc)
            cmd_parser.set_defaults(func=execute_repo_command, cmd=cmd, repo=r)

    for a in l.get_algorithms():
        alg_parser = subparsers.add_parser(a, help='commands for algorithm '+ a +'')
        alg_subparser = alg_parser.add_subparsers(help=a+' algorithm commands.')
        for cmd, desc in l.get_algorithm_commands(a):
            alg_parser = alg_subparser.add_parser(cmd, help=desc)
            alg_parser.set_defaults(func=execute_algorithm, cmd=cmd, alg=a)

    args = parser.parse_args()
    if 'func' in args:
        args.func(args)
    else:
        parser.print_help()

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before># Python Security Project (PySec) and its related class files.
#
# PySec is a set of tools for secure application development under Linux
#
# Copyright 2014 PySec development team
#
# 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.
"""Module to manage more efficently import of modules"""
import imp
import os
import hashlib
import string
import base64
from types import ModuleType

_HASHES = {
    'md5': getattr(hashlib, 'md5'),
    'sha1': getattr(hashlib, 'sha1'),
    'sha256': getattr(hashlib, 'sha256'),
    'sha512': getattr(hashlib, 'sha512'),
}

_FIRST_LETTERS = '_%s' % string.ascii_letters
_OTHER_LETTERS = '_%s%s' % (string.ascii_letters, string.digits)

is_hex = lambda s: all(ch in string.hexdigits for ch in s)

def check_libname(name):
    name = str(name)
    if not name:
        return 0
    return (name[0] in _FIRST_LETTERS and
            all(ch in _OTHER_LETTERS for ch in name[1:]))

def parse_version(version):
    version = str(version).split('.')
    if len(version) != 3:
        return None
    if all(vs.isdigit() for vs in version):
        return tuple(int(vs) for vs in version)
    return None

def parse_hashes(hashes):
    _hashes = {}
    if hashes:
        for hs in hashes.split(' '):
            hname, _, hval = hs.partition(':')
            hs = _HASHES.get(hname, None)
            if hs is None:
                return None
            if not is_hex(hval):
                return None
            if hs in _hashes:
                return None
            _hashes[hs] = hval
    return _hashes

def _hash(path, hs):
    with open(path, 'rb') as fp:
        chunk = fp.read(4096)
        while chunk:
            hs.update(chunk)
            chunk = fp.read(4096)

def get_hash(path, hs):
    hs = hs()
    if os.path.isfile(path):
        files = [path]
    elif os.path.isdir(path):
        files = sorted([os.path.join(dirpath, fname)
                       for dirpath, _, filenames in os.walk(path)
                       for fname in filenames
                       if os.path.isfile(os.path.join(dirpath, fname))])
    else:
        raise ImportError("invalid file type %r" % path)
    for fpath in files:
        _hash(fpath, hs)
    return hs.hexdigest()

_CACHE = {}
_TAB = {}

class __LazyModule(ModuleType):

    def __init___(self, name, version):
        self.name = str(name)
        self.version = version
        self.module = None

    def __getattr__(self, name):
        return getattr(self.module or importlib(self.name, self.version), name)

    def __setattr__(self, name, value):
        return setattr(self.module or importlib(self.name, self.version),
                       name, value)

    def __delattr__(self, name):
        return delattr(self.module or importlib(self.name, self.version), name)

def load_tab(path):
    path = os.path.abspath(str(path))
    _tab = {}
    with fd.File.open(path, fd.FO_READ) as ftab:
        for lineno, line in enumerate(ftab.lines()):
            fields = line.strip().split(';')
            # name, version, path, hashes
            if len(fields) != 4:
                raise ImportError("wrong number of fields at line %d" % lineno)
            name, version, path, hashes = fields
            # name
            if not check_libname(name):
                raise ImportError("wrong library name at line %d" % lineno)
            # version
            version = parse_version(version)
            if version is None:
                raise ImportError("wrong version format at line %d" % lineno)
            # path
            path = os.path.abspath(base64.b64decode(path))
            # hashes
            hashes = parse_hashes(hashes)
            if hashes is None:
                raise ImportError("wrong hash format at line %d" % lineno)
            # update tab
            mod_vers = _tab.setdefault(name, {})
            if version in mod_vers:
                raise ImportError("duplicated library: %r %d.%d.%d"
                                  % (name, version[0], version[1], version[2]))
            mod_vers[version] = {'path': path, 'hash': hashes}
    _TAB.update(_tab)

def importlib(name, version=None, lazy=0, reload=0):
    """Load a library and return it.
    name        library's name
    version     if it's None it load lastest library, otherwise load the
                version specified
    lazy        if false it returns normal module, otherwise it returns a
                module placeholder and it will be loaded the first time that
                it will be used
    reload      if false search library in cache and returns it if exists
                otherwise it load it. If reload is true load library anse save
                it in cache
    """
    name = str(name)
    vers = _TAB.get(name, None)
    if vers is None:
        raise ImportError("library %r not found" % name)
    if version is None:
        version = max(vers.iterkeys())
    elif version not in vers:
        raise ImportError("library %r %r not found" % (name, version))
    if not reload and (name, version) in _CACHE:
        return _CACHE[(name, version)]
    mod_info = vers.get(version)
    try:
        imp.acquire_lock()
        path = mod_info['path']
        if lazy:
            return __LazyModule(name, path)
        else:
            fdir, fname = os.path.split(path)
            for hs, hval in mod_info['hash'].iteritems():
                if get_hash(path, hs) != hval:
                    raise ImportError("module %r %r in %r don't match hash %r"
                                      % (name, version, path, hval))
            desc = imp.find_module(os.path.splitext(fname)[0], [fdir])
            mod = imp.load_module(name, *desc)
            _CACHE[(name, version)] = mod
            return mod
    finally:
        imp.release_lock()

def make_line(path, name, version):
    """Makes a complete string for loader's file"""
    # name, version, path, hashes
    path = os.path.abspath(path)
    path64 = base64.b64encode(path)
    name = str(name)
    # XXX check
    version = tuple(version)
    hashes = []
    for hs_name, hs_func in _HASHES.iteritems():
        hashes.append('%s:%s' % (hs_name, get_hash(path, hs_func)))
    return '%s;%s;%s;%s' % (str(name), '.'.join(str(vs) for vs in version),
                            path64, ' '.join(hashes))
<commit_msg>remove string module and use internal constants<commit_after># Python Security Project (PySec) and its related class files.
#
# PySec is a set of tools for secure application development under Linux
#
# Copyright 2014 PySec development team
#
# 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.
"""Module to manage more efficently import of modules"""
import imp
import os
import hashlib
import base64
from types import ModuleType

ASCII_LETTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
DIGITS = '0123456789'

_HASHES = {
    'md5': getattr(hashlib, 'md5'),
    'sha1': getattr(hashlib, 'sha1'),
    'sha256': getattr(hashlib, 'sha256'),
    'sha512': getattr(hashlib, 'sha512'),
}

_FIRST_LETTERS = '_%s' % string.ASCII_LETTERS
_OTHER_LETTERS = '_%s%s' % (string.ASCII_LETTERS, string.DIGITS)

is_hex = lambda s: all(ch in string.hexdigits for ch in s)

def check_libname(name):
    name = str(name)
    if not name:
        return 0
    return (name[0] in _FIRST_LETTERS and
            all(ch in _OTHER_LETTERS for ch in name[1:]))

def parse_version(version):
    version = str(version).split('.')
    if len(version) != 3:
        return None
    if all(vs.isdigit() for vs in version):
        return tuple(int(vs) for vs in version)
    return None

def parse_hashes(hashes):
    _hashes = {}
    if hashes:
        for hs in hashes.split(' '):
            hname, _, hval = hs.partition(':')
            hs = _HASHES.get(hname, None)
            if hs is None:
                return None
            if not is_hex(hval):
                return None
            if hs in _hashes:
                return None
            _hashes[hs] = hval
    return _hashes

def _hash(path, hs):
    with open(path, 'rb') as fp:
        chunk = fp.read(4096)
        while chunk:
            hs.update(chunk)
            chunk = fp.read(4096)

def get_hash(path, hs):
    hs = hs()
    if os.path.isfile(path):
        files = [path]
    elif os.path.isdir(path):
        files = sorted([os.path.join(dirpath, fname)
                       for dirpath, _, filenames in os.walk(path)
                       for fname in filenames
                       if os.path.isfile(os.path.join(dirpath, fname))])
    else:
        raise ImportError("invalid file type %r" % path)
    for fpath in files:
        _hash(fpath, hs)
    return hs.hexdigest()

_CACHE = {}
_TAB = {}

class __LazyModule(ModuleType):

    def __init___(self, name, version):
        self.name = str(name)
        self.version = version
        self.module = None

    def __getattr__(self, name):
        return getattr(self.module or importlib(self.name, self.version), name)

    def __setattr__(self, name, value):
        return setattr(self.module or importlib(self.name, self.version),
                       name, value)

    def __delattr__(self, name):
        return delattr(self.module or importlib(self.name, self.version), name)

def load_tab(path):
    path = os.path.abspath(str(path))
    _tab = {}
    with fd.File.open(path, fd.FO_READ) as ftab:
        for lineno, line in enumerate(ftab.lines()):
            fields = line.strip().split(';')
            # name, version, path, hashes
            if len(fields) != 4:
                raise ImportError("wrong number of fields at line %d" % lineno)
            name, version, path, hashes = fields
            # name
            if not check_libname(name):
                raise ImportError("wrong library name at line %d" % lineno)
            # version
            version = parse_version(version)
            if version is None:
                raise ImportError("wrong version format at line %d" % lineno)
            # path
            path = os.path.abspath(base64.b64decode(path))
            # hashes
            hashes = parse_hashes(hashes)
            if hashes is None:
                raise ImportError("wrong hash format at line %d" % lineno)
            # update tab
            mod_vers = _tab.setdefault(name, {})
            if version in mod_vers:
                raise ImportError("duplicated library: %r %d.%d.%d"
                                  % (name, version[0], version[1], version[2]))
            mod_vers[version] = {'path': path, 'hash': hashes}
    _TAB.update(_tab)

def importlib(name, version=None, lazy=0, reload=0):
    """Load a library and return it.
    name        library's name
    version     if it's None it load lastest library, otherwise load the
                version specified
    lazy        if false it returns normal module, otherwise it returns a
                module placeholder and it will be loaded the first time that
                it will be used
    reload      if false search library in cache and returns it if exists
                otherwise it load it. If reload is true load library anse save
                it in cache
    """
    name = str(name)
    vers = _TAB.get(name, None)
    if vers is None:
        raise ImportError("library %r not found" % name)
    if version is None:
        version = max(vers.iterkeys())
    elif version not in vers:
        raise ImportError("library %r %r not found" % (name, version))
    if not reload and (name, version) in _CACHE:
        return _CACHE[(name, version)]
    mod_info = vers.get(version)
    try:
        imp.acquire_lock()
        path = mod_info['path']
        if lazy:
            return __LazyModule(name, path)
        else:
            fdir, fname = os.path.split(path)
            for hs, hval in mod_info['hash'].iteritems():
                if get_hash(path, hs) != hval:
                    raise ImportError("module %r %r in %r don't match hash %r"
                                      % (name, version, path, hval))
            desc = imp.find_module(os.path.splitext(fname)[0], [fdir])
            mod = imp.load_module(name, *desc)
            _CACHE[(name, version)] = mod
            return mod
    finally:
        imp.release_lock()

def make_line(path, name, version):
    """Makes a complete string for loader's file"""
    # name, version, path, hashes
    path = os.path.abspath(path)
    path64 = base64.b64encode(path)
    name = str(name)
    version = tuple(version)
    hashes = []
    for hs_name, hs_func in _HASHES.iteritems():
        hashes.append('%s:%s' % (hs_name, get_hash(path, hs_func)))
    return '%s;%s;%s;%s' % (str(name), '.'.join(str(vs) for vs in version),
                            path64, ' '.join(hashes))
<|endoftext|>


<commit_before># coding=utf-8
"""This is a script for running pytest from the command line.

This script exists so that the project directory gets added to sys.path, which
prevents us from accidentally testing the globally installed willie version.

pytest_run.py
Copyright 2013, Ari Koivula, <ari@koivu.la>
Licensed under the Eiffel Forum License 2.

http://willie.dfbta.net
"""

if __name__ == "__main__":
    import sys
    import pytest
    returncode = pytest.main()
    sys.exit(returncode)
<commit_msg>[tests] Make testing script executable<commit_after># coding=utf-8
"""This is a script for running pytest from the command line.

This script exists so that the project directory gets added to sys.path, which
prevents us from accidentally testing the globally installed willie version.

pytest_run.py
Copyright 2013, Ari Koivula, <ari@koivu.la>
Licensed under the Eiffel Forum License 2.

http://willie.dfbta.net
"""

if __name__ == "__main__":
    import sys
    import pytest
    returncode = pytest.main()
    sys.exit(returncode)
<|endoftext|>


<commit_before>#!/usr/bin/env python

from ctypes import POINTER, Structure, cdll, c_char_p, c_uint, c_int, \
		create_string_buffer, byref
from sys import argv

def _checkNull(result, func, arguments):
	if result:
		return result
	else:
		raise IOError("Null pointer")

def _checkRW(result, func, arguments):
	if result > 0:
		return result
	else:
		raise IOError("I/O error")

def _init():
	class _Context(Structure):
		pass
	class _Device(Structure):
		pass
	class _Channel(Structure):
		pass

	ContextPtr = POINTER(_Context)
	DevicePtr = POINTER(_Device)
	ChannelPtr = POINTER(_Channel)

	lib = cdll.LoadLibrary('libiio.so.0')

	global _new_local
	_new_local = lib.iio_create_local_context
	_new_local.restype = ContextPtr
	_new_local.archtypes = None
	_new_local.errcheck = _checkNull

	global _destroy
	_destroy = lib.iio_context_destroy
	_destroy.restype = None
	_destroy.archtypes = (ContextPtr, )

	global _get_name
	_get_name = lib.iio_context_get_name
	_get_name.restype = c_char_p
	_get_name.archtypes = (ContextPtr, )
	_get_name.errcheck = _checkNull

	global _devices_count
	_devices_count = lib.iio_context_get_devices_count
	_devices_count.restype = c_uint
	_devices_count.archtypes = (ContextPtr, )

	global _get_device
	_get_device = lib.iio_context_get_device
	_get_device.restype = DevicePtr
	_get_device.archtypes = (ContextPtr, c_uint)
	_get_device.errcheck = _checkNull

	global _d_get_id
	_d_get_id = lib.iio_device_get_id
	_d_get_id.restype = c_char_p
	_d_get_id.archtypes = (DevicePtr, )
	_d_get_id.errcheck = _checkNull

	global _d_get_name
	_d_get_name = lib.iio_device_get_name
	_d_get_name.restype = c_char_p
	_d_get_name.archtypes = (DevicePtr, )

	global _d_attr_count
	_d_attr_count = lib.iio_device_get_attrs_count
	_d_attr_count.restype = c_uint
	_d_attr_count.archtypes = (DevicePtr, )

	global _d_get_attr
	_d_get_attr = lib.iio_device_get_attr
	_d_get_attr.restype = c_char_p
	_d_get_attr.archtypes = (DevicePtr, )
	_d_get_attr.errcheck = _checkNull

	global _d_read_attr
	_d_read_attr = lib.iio_device_attr_read
	_d_read_attr.restype = c_int
	_d_read_attr.archtypes = (DevicePtr, c_char_p, c_char_p, c_uint)
	_d_read_attr.errcheck = _checkRW

	global _d_write_attr
	_d_write_attr = lib.iio_device_attr_write
	_d_write_attr.restype = c_int
	_d_write_attr.archtypes = (DevicePtr, c_char_p, c_char_p)
	_d_write_attr.errcheck = _checkRW

	global _channels_count
	_channels_count = lib.iio_device_get_channels_count
	_channels_count.restype = c_uint
	_channels_count.archtypes = (DevicePtr, )

	global _get_channel
	_get_channel = lib.iio_device_get_channel
	_get_channel.restype = ChannelPtr
	_get_channel.archtypes = (DevicePtr, c_uint)
	_get_channel.errcheck = _checkNull

	global _c_get_id
	_c_get_id = lib.iio_channel_get_id
	_c_get_id.restype = c_char_p
	_c_get_id.archtypes = (ChannelPtr, )
	_c_get_id.errcheck = _checkNull

	global _c_get_name
	_c_get_name = lib.iio_channel_get_name
	_c_get_name.restype = c_char_p
	_c_get_name.archtypes = (ChannelPtr, )

	global _c_get_type
	_c_get_type = lib.iio_channel_get_type
	_c_get_type.restype = c_uint
	_c_get_type.archtypes = (ChannelPtr, )

	global _c_attr_count
	_c_attr_count = lib.iio_channel_get_attrs_count
	_c_attr_count.restype = c_uint
	_c_attr_count.archtypes = (ChannelPtr, )

	global _c_get_attr
	_c_get_attr = lib.iio_channel_get_attr
	_c_get_attr.restype = c_char_p
	_c_get_attr.archtypes = (ChannelPtr, )
	_c_get_attr.errcheck = _checkNull

	global _c_read_attr
	_c_read_attr = lib.iio_channel_attr_read
	_c_read_attr.restype = c_int
	_c_read_attr.archtypes = (ChannelPtr, c_char_p, c_char_p, c_uint)
	_c_read_attr.errcheck = _checkRW

	global _c_write_attr
	_c_write_attr = lib.iio_channel_attr_write
	_c_write_attr.restype = c_int
	_c_write_attr.archtypes = (ChannelPtr, c_char_p, c_char_p)
	_c_write_attr.errcheck = _checkRW

_init()

class Channel(object):
	def __init__(self, _channel):
		self._channel = _channel
		self.attrs = [ _c_get_attr(self._channel, x) \
				for x in xrange(0, _c_attr_count(self._channel)) ]
		self.id = _c_get_id(self._channel)
		self.name = _c_get_name(self._channel)
		self.output = _c_get_type(self._channel) == 1

	# TODO(pcercuei): Provide a dict-like interface for the attributes
	def read_attr(self, attr):
		buf = create_string_buffer(1024)
		_c_read_attr(self._channel, attr, buf, 1024)
		return buf.value

	def write_attr(self, attr, value):
		_c_write_attr(self._channel, attr, value)

class Device(object):
	def __init__(self, _device):
		self._device = _device
		self.attrs = [ _d_get_attr(self._device, x) \
				for x in xrange(0, _d_attr_count(self._device)) ]
		self.channels = [ Channel(_get_channel(self._device, x)) \
				for x in xrange(0, _channels_count(self._device)) ]
		self.id = _d_get_id(self._device)
		self.name = _d_get_name(self._device)

	# TODO(pcercuei): Provide a dict-like interface for the attributes
	def read_attr(self, attr):
		buf = create_string_buffer(1024)
		_d_read_attr(self._device, attr, byref(buf), 1024)
		return buf.value

	def write_attr(self, attr, value):
		_d_write_attr(self._device, attr, value)

class LocalContext(object):
	def __init__(self):
		self._context = _new_local()
		nb_devices = _devices_count(self._context)
		self.devices = [ Device(_get_device(self._context, x)) \
				for x in xrange(0, _devices_count(self._context)) ]
		self.name = _get_name(self._context)

	def __del__(self):
		_destroy(self._context)
<commit_msg>Added Python bindings for creating XML contexts<commit_after>#!/usr/bin/env python

from ctypes import POINTER, Structure, cdll, c_char_p, c_uint, c_int, \
		create_string_buffer, byref
from sys import argv

def _checkNull(result, func, arguments):
	if result:
		return result
	else:
		raise IOError("Null pointer")

def _checkRW(result, func, arguments):
	if result > 0:
		return result
	else:
		raise IOError("I/O error")

def _init():
	class _Context(Structure):
		pass
	class _Device(Structure):
		pass
	class _Channel(Structure):
		pass

	ContextPtr = POINTER(_Context)
	DevicePtr = POINTER(_Device)
	ChannelPtr = POINTER(_Channel)

	lib = cdll.LoadLibrary('libiio.so.0')

	global _new_local
	_new_local = lib.iio_create_local_context
	_new_local.restype = ContextPtr
	_new_local.archtypes = None
	_new_local.errcheck = _checkNull

	global _new_xml
	_new_xml = lib.iio_create_xml_context
	_new_xml.restype = ContextPtr
	_new_xml.archtypes = (c_char_p, )
	_new_xml.errcheck = _checkNull

	global _destroy
	_destroy = lib.iio_context_destroy
	_destroy.restype = None
	_destroy.archtypes = (ContextPtr, )

	global _get_name
	_get_name = lib.iio_context_get_name
	_get_name.restype = c_char_p
	_get_name.archtypes = (ContextPtr, )
	_get_name.errcheck = _checkNull

	global _devices_count
	_devices_count = lib.iio_context_get_devices_count
	_devices_count.restype = c_uint
	_devices_count.archtypes = (ContextPtr, )

	global _get_device
	_get_device = lib.iio_context_get_device
	_get_device.restype = DevicePtr
	_get_device.archtypes = (ContextPtr, c_uint)
	_get_device.errcheck = _checkNull

	global _d_get_id
	_d_get_id = lib.iio_device_get_id
	_d_get_id.restype = c_char_p
	_d_get_id.archtypes = (DevicePtr, )
	_d_get_id.errcheck = _checkNull

	global _d_get_name
	_d_get_name = lib.iio_device_get_name
	_d_get_name.restype = c_char_p
	_d_get_name.archtypes = (DevicePtr, )

	global _d_attr_count
	_d_attr_count = lib.iio_device_get_attrs_count
	_d_attr_count.restype = c_uint
	_d_attr_count.archtypes = (DevicePtr, )

	global _d_get_attr
	_d_get_attr = lib.iio_device_get_attr
	_d_get_attr.restype = c_char_p
	_d_get_attr.archtypes = (DevicePtr, )
	_d_get_attr.errcheck = _checkNull

	global _d_read_attr
	_d_read_attr = lib.iio_device_attr_read
	_d_read_attr.restype = c_int
	_d_read_attr.archtypes = (DevicePtr, c_char_p, c_char_p, c_uint)
	_d_read_attr.errcheck = _checkRW

	global _d_write_attr
	_d_write_attr = lib.iio_device_attr_write
	_d_write_attr.restype = c_int
	_d_write_attr.archtypes = (DevicePtr, c_char_p, c_char_p)
	_d_write_attr.errcheck = _checkRW

	global _channels_count
	_channels_count = lib.iio_device_get_channels_count
	_channels_count.restype = c_uint
	_channels_count.archtypes = (DevicePtr, )

	global _get_channel
	_get_channel = lib.iio_device_get_channel
	_get_channel.restype = ChannelPtr
	_get_channel.archtypes = (DevicePtr, c_uint)
	_get_channel.errcheck = _checkNull

	global _c_get_id
	_c_get_id = lib.iio_channel_get_id
	_c_get_id.restype = c_char_p
	_c_get_id.archtypes = (ChannelPtr, )
	_c_get_id.errcheck = _checkNull

	global _c_get_name
	_c_get_name = lib.iio_channel_get_name
	_c_get_name.restype = c_char_p
	_c_get_name.archtypes = (ChannelPtr, )

	global _c_get_type
	_c_get_type = lib.iio_channel_get_type
	_c_get_type.restype = c_uint
	_c_get_type.archtypes = (ChannelPtr, )

	global _c_attr_count
	_c_attr_count = lib.iio_channel_get_attrs_count
	_c_attr_count.restype = c_uint
	_c_attr_count.archtypes = (ChannelPtr, )

	global _c_get_attr
	_c_get_attr = lib.iio_channel_get_attr
	_c_get_attr.restype = c_char_p
	_c_get_attr.archtypes = (ChannelPtr, )
	_c_get_attr.errcheck = _checkNull

	global _c_read_attr
	_c_read_attr = lib.iio_channel_attr_read
	_c_read_attr.restype = c_int
	_c_read_attr.archtypes = (ChannelPtr, c_char_p, c_char_p, c_uint)
	_c_read_attr.errcheck = _checkRW

	global _c_write_attr
	_c_write_attr = lib.iio_channel_attr_write
	_c_write_attr.restype = c_int
	_c_write_attr.archtypes = (ChannelPtr, c_char_p, c_char_p)
	_c_write_attr.errcheck = _checkRW

_init()

class Channel(object):
	def __init__(self, _channel):
		self._channel = _channel
		self.attrs = [ _c_get_attr(self._channel, x) \
				for x in xrange(0, _c_attr_count(self._channel)) ]
		self.id = _c_get_id(self._channel)
		self.name = _c_get_name(self._channel)
		self.output = _c_get_type(self._channel) == 1

	# TODO(pcercuei): Provide a dict-like interface for the attributes
	def read_attr(self, attr):
		buf = create_string_buffer(1024)
		_c_read_attr(self._channel, attr, buf, 1024)
		return buf.value

	def write_attr(self, attr, value):
		_c_write_attr(self._channel, attr, value)

class Device(object):
	def __init__(self, _device):
		self._device = _device
		self.attrs = [ _d_get_attr(self._device, x) \
				for x in xrange(0, _d_attr_count(self._device)) ]
		self.channels = [ Channel(_get_channel(self._device, x)) \
				for x in xrange(0, _channels_count(self._device)) ]
		self.id = _d_get_id(self._device)
		self.name = _d_get_name(self._device)

	# TODO(pcercuei): Provide a dict-like interface for the attributes
	def read_attr(self, attr):
		buf = create_string_buffer(1024)
		_d_read_attr(self._device, attr, byref(buf), 1024)
		return buf.value

	def write_attr(self, attr, value):
		_d_write_attr(self._device, attr, value)

class Context(object):
	def __init__(self, _context):
		self._context = _context
		nb_devices = _devices_count(self._context)
		self.devices = [ Device(_get_device(self._context, x)) \
				for x in xrange(0, _devices_count(self._context)) ]
		self.name = _get_name(self._context)

	def __del__(self):
		_destroy(self._context)

class LocalContext(Context):
	def __init__(self):
		ctx = _new_local()
		super(LocalContext, self).__init__(ctx)

class XMLContext(Context):
	def __init__(self, xmlfile):
		ctx = _new_xml(xmlfile)
		super(XMLContext, self).__init__(ctx)
<|endoftext|>