<commit_before># Licensed under a 3-clause BSD style license - see LICENSE.rst.
"""
OPEN ASTRONOMY CATALOG (OAC) API TOOL
-------------------------------------
This module allows access to the OAC API and
all available functionality. For more information
see: https://api.astrocats.space.
:authors: Philip S. Cowperthwaite (pcowpert@cfa.harvard.edu)
and James Guillochon (jguillochon@cfa.harvard.edu)
"""

import json
import csv

import astropy.units as u
from astropy.table import Column, Table
from astroquery import log

from . import conf
from ..query import BaseQuery
from ..utils import async_to_sync, commons

__all__ = ['OAC', 'OACClass']

@async_to_sync
class OACClass(BaseQuery):
    """OAC class."""

    URL = conf.server
    TIMEOUT = conf.timeout
    HEADERS = {'Content-type': 'application/json', 'Accept': 'text/plain'}
    FORMAT = None

    def query_object_async(self,
                           event,
                           quantity=None,
                           attribute=None,
                           argument=None,
                           data_format='csv',
                           get_query_payload=False, cache=True):
        """
        Retrieve object(s) asynchronously.

        Query method to retrieve the desired quantities and
        attributes for an object specified by a transient name.

        If no quantities or attributes are given then the query
        returns the top-level metadata about the event(s).

        The complete list of available quantities and attributes
        can be found at https://github.com/astrocatalogs/schema.

        Parameters
        ----------
        event : str or list, required
            Name of the event to query. Can be a list
            of event names.
        quantity : str or list, optional
            Name of quantity to retrieve. Can be
            a list of quantities. The default is None.
        attribute : str or list, optional
            Name of specific attributes to retrieve. Can be a list
            of attributes. The default is None.
        argument : str or list, optional
            These are special conditional arguments that can be applied
            to a query to refine.
            Examples include: 'band=i' returns only i-band photometry,
            'first' returns the first result, 'sortby=attribute' returns
            a table sorted by the given attribute, and 'complete' returns
            only those table rows with all of the requested attributes.
            A complete list of commands and their usage can be found at:
            https://github.com/astrocatalogs/OACAPI. The default is None.
        data_format: str, optional
            Specify the format for the returned data. The default is
            CSV for easy conversion to Astropy Tables. The user can
            also specify JSON which will return a JSON-compliant
            dictionary.
            Note: Not all queries can support CSV output.
        get_query_payload : bool, optional
            When set to `True` the method returns the HTTP request
            parameters as a dict. The actual HTTP request is not made.
            The default value is False.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        request_payload = self._args_to_payload(event,
                                                quantity,
                                                attribute,
                                                argument,
                                                data_format)

        if get_query_payload:
            return request_payload

        response = self._request('GET', self.URL,
                                 data=json.dumps(request_payload),
                                 timeout=self.TIMEOUT,
                                 headers=self.HEADERS,
                                 cache=cache)

        return response

    def query_region_async(self, coordinates,
                           radius=None,
                           height=None, width=None,
                           quantity=None,
                           attribute=None,
                           argument=None,
                           data_format='csv',
                           get_query_payload=False, cache=True):
        """
        Query a region asynchronously.

        Query method to retrieve the desired quantities and
        attributes for an object specified by a region on the sky.
        The search can be either a cone search (using the radius
        parameter) or a box search (using the width/height parameters).

        IMPORTANT: The API can only query a single set of coordinates
        at a time.

        The complete list of available quantities and attributes
        can be found at https://github.com/astrocatalogs/schema.

        Parameters
        ----------
        coordinates : str or `astropy.coordinates`.
            A single set of ra/dec coorindates to query. Can be either
            a list with [ra,dec] or an astropy coordinates object.
            Can be given in sexigesimal or decimal format. The API
            can not query multiple sets of coordinates.
        radius : str, float or `astropy.units.Quantity`, optional
            The radius, in arcseconds, of the cone search centered
            on coordinates. Should be a single, float-convertable value
            or an astropy quantity. The default value is None.
        width : str, float or `astropy.units.Quantity`, optional
            The width, in arcseconds, of the box search centered
            on coordinates. Should be a single, float-convertable value
            or an astropy quantity. The default value is None.
        height : str, float or `astropy.units.Quantity`, optional
            The height, in arcseconds, of the box search centered
            on coordinates. Should be a single, float-convertable value
            or an astropy quantity. The default value is None.
        quantity: str or list, optional
            Name of quantity to retrieve. Can be a
            a list of quantities. The default is None.
        attribute: str or list, optional
            Name of specific attributes to retrieve. Can be a list
            of attributes. The default is None.
        argument : str or list, optional
            These are special conditional arguments that can be applied
            to a query to refine.
            Examples include: 'band=i' returns only i-band photometry,
            'first' returns the first result, 'sortby=attribute' returns
            a table sorted by the given attribute, and 'complete' returns
            only those table rows with all of the requested attributes.
            A complete list of commands and their usage can be found at:
            https://github.com/astrocatalogs/OACAPI. The default is None.
        data_format: str, optional
            Specify the format for the returned data. The default is
            CSV for easy conversion to Astropy Tables. The user can
            also specify JSON which will return a JSON-compliant
            dictionary.
            Note: Not all queries can support CSV output.
        get_query_payload : bool, optional
            When set to `True` the method returns the HTTP request
            parameters as a dict. The actual HTTP request is not made.
            The default value is False.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        # Default object name used for coordinate-based queries
        event = 'catalog'

        request_payload = self._args_to_payload(event,
                                                quantity,
                                                attribute,
                                                argument,
                                                data_format)

        # Check that coordinate object is a valid astropy coordinate object
        # Criteria/Code from ../sdss/core.py
        if (not isinstance(coordinates, list) and
            not isinstance(coordinates, Column) and
            not (isinstance(coordinates, commons.CoordClasses) and
                 not coordinates.isscalar)):

            request_payload['ra'] = coordinates.ra.deg
            request_payload['dec'] = coordinates.dec.deg

        else:
            try:
                request_payload['ra'] = coordinates[0]
                request_payload['dec'] = coordinates[1]
            except IndexError:
                raise IndexError("Please check format of input coordinates.")

        if ((radius is None) and (height is None) and (width is None)):
            raise ValueError("Please enter a radius or width/height pair.")

        if (radius is not None and (height is not None or width is not None)):
            raise ValueError("Please specify ONLY a radius or "
                             "height/width pair.")

        if ((radius is None) and ((height is None) or (width is None))):
            raise ValueError("Please enter both a width and height "
                             "for a box search.")

        # Check that any values are in the proper format.
        # Criteria/Code from ../sdss/core.py
        if radius is not None:
            if isinstance(radius, u.Quantity):
                radius = radius.to(u.arcsec).value
            else:
                try:
                    float(radius)
                except TypeError:
                    raise TypeError("radius should be either Quantity or "
                                    "convertible to float.")

            request_payload['radius'] = radius

        if (width is not None and height is not None):
            if isinstance(width, u.Quantity):
                width = width.to(u.arcsec).value
            else:
                try:
                    float(width)
                except TypeError:
                    raise TypeError("width should be either Quantity or "
                                    "convertible to float.")

            if isinstance(height, u.Quantity):
                height = height.to(u.arcmin).value
            else:
                try:
                    float(height)
                except TypeError:
                    raise TypeError("height should be either Quantity or "
                                    "convertible to float.")

            request_payload['width'] = width
            request_payload['height'] = height

        if get_query_payload:
            return request_payload

        response = self._request('GET', self.URL,
                                 data=json.dumps(request_payload),
                                 timeout=self.TIMEOUT,
                                 headers=self.HEADERS,
                                 cache=cache)

        return response

    def get_photometry_async(self, event, argument=None, cache=True):
        """
        Retrieve all photometry for specified event(s).

        This is a version of the query_object method
        that is set up to quickly return the complete set
        of light curve(s) for the given event(s).

        The light curves are returned by default as an
        Astropy Table.

        Additional arguments can be specified but more complicated
        queries should make use of the base query_object method
        instead of get_photometry.

        Parameters
        ----------
        event : str or list, required
            Name of the event to query. Can be a list
            of event names.
        argument : str or list, optional
            These are special conditional arguments that can be applied
            to a query to refine.
            Examples include: 'band=i' returns only i-band photometry,
            'first' returns the first result, 'sortby=attribute' returns
            a table sorted by the given attribute, and 'complete' returns
            only those table rows with all of the requested attributes.
            A complete list of commands and their usage can be found at:
            https://github.com/astrocatalogs/OACAPI. The default is None.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        response = self.query_object_async(event=event,
                                           quantity='photometry',
                                           attribute=['time', 'magnitude',
                                                      'e_magnitude', 'band',
                                                      'instrument'],
                                           argument=argument,
                                           cache=cache
                                           )

        return response

    def get_single_spectrum_async(self, event, time, cache=True):
        """
        Retrieve a single spectrum at a specified time for given event.

        This is a version of the query_object method
        that is set up to quickly return a single spectrum
        at a user-specified time. The time does not have to be
        precise as the method uses the closest option by default.

        The spectrum is returned as an astropy table.

        More complicated queries, or queries requesting multiple spectra,
        should make use of the base query_object or get_spectra methods.

        Parameters
        ----------
        event : str, required
            Name of the event to query. Must be a single event.
        time : float, required
            A single MJD time to query. This time does not need to be
            exact. The closest spectrum will be returned.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        query_time = 'time=%s' % time
        response = self.query_object_async(event=event,
                                           quantity='spectra',
                                           attribute=['data'],
                                           argument=[query_time, 'closest'],
                                           cache=cache
                                           )

        return response

    def get_spectra_async(self, event, cache=True):
        """
        Retrieve all spectra for a specified event.

        This is a version of the query_object method
        that is set up to quickly return all available spectra
        for a single event.

        The spectra must be returned as a JSON-compliant dictionary.
        Multiple spectra can not be unwrapped into a csv/Table.

        More complicated queries should make use of the
        base query_object methods.

        Parameters
        ----------
        event : str, required
            Name of the event to query. Can be a single event or a
            list of events.

        Returns
        -------
        result : dict
            The default result is a JSON dictionary. An `~astropy.table.Table`
            can not be returned.
        """
        response = self.query_object_async(event=event,
                                           quantity='spectra',
                                           attribute=['time', 'data'],
                                           data_format='json',
                                           cache=cache
                                           )
        return response

    def _args_to_payload(self, event, quantity,
                         attribute, argument, data_format):
        request_payload = dict()

        if (event) and (not isinstance(event, list)):
            event = [event]

        if (quantity) and (not isinstance(quantity, list)):
            quantity = [quantity]

        if (attribute) and (not isinstance(attribute, list)):
            attribute = [attribute]

        if (argument) and (not isinstance(argument, list)):
            argument = [argument]

        request_payload['event'] = event
        request_payload['quantity'] = quantity
        request_payload['attribute'] = attribute

        if argument:
            if 'format' in argument:
                raise KeyError("Please specify the output format using the "
                               "data_format function argument")
            if 'radius' in argument:
                raise KeyError("A search radius should be specified "
                               "explicitly using the query_region method.")
            if 'width' in argument:
                raise KeyError("A search width should be specified "
                               "explicitly using the query_region method.")
            if 'height' in argument:
                raise KeyError("A search height should be specified "
                               "explicitly using the query_region method.")

            for arg in argument:
                if '=' in arg:
                    split_arg = arg.split('=')
                    request_payload[split_arg[0]] = split_arg[1]
                else:
                    request_payload[arg] = True

        if ((data_format.lower() == 'csv') or
                (data_format.lower() == 'json')):
            request_payload['format'] = data_format.lower()
        else:
            raise ValueError("The format must be either csv or JSON.")

        self.FORMAT = data_format.lower()

        return request_payload

    def _format_output(self, raw_output):
        if self.FORMAT == 'csv':
            split_output = raw_output.splitlines()
            columns = list(csv.reader([split_output[0]], delimiter=',',
                           quotechar='"'))[0]
            rows = split_output[1:]

            # Quick test to see if API returned a valid csv file
            # If not, try to return JSON-compliant dictionary.
            test_row = list(csv.reader([rows[0]], delimiter=',',
                            quotechar='"'))[0]

            if (len(columns) != len(test_row)):
                log.info("The API did not return a valid CSV output! \n"
                      "Outputing JSON-compliant dictionary instead.")

                output = json.loads(raw_output)
                return output

            # Initialize and populate dictionary
            output_dict = {key: [] for key in columns}

            for row in rows:

                split_row = list(csv.reader([row], delimiter=',',
                                 quotechar='"'))[0]

                for ct, key in enumerate(columns):
                    output_dict[key].append(split_row[ct])

            # Convert dictionary to Astropy Table.
            output = Table(output_dict, names=columns)

        else:
            # Server response is JSON compliant. Simply
            # convert from raw text to dictionary.
            output = json.loads(raw_output)

        return output

    def _parse_result(self, response, verbose=False):
        if not verbose:
            commons.suppress_vo_warnings()

        if response.status_code != 200:
            raise AttributeError("ERROR: The web service returned error code: %s" %
                response.status_code)

        if 'message' in response.text:
            raise KeyError("ERROR: API Server returned the following error:\n{}".format(response.text))

        raw_output = response.text
        output_response = self._format_output(raw_output)

        return output_response

OAC = OACClass()
<commit_msg>Fix bug in parsing text containing html tag<commit_after># Licensed under a 3-clause BSD style license - see LICENSE.rst.
"""
OPEN ASTRONOMY CATALOG (OAC) API TOOL
-------------------------------------
This module allows access to the OAC API and
all available functionality. For more information
see: https://api.astrocats.space.
:authors: Philip S. Cowperthwaite (pcowpert@cfa.harvard.edu)
and James Guillochon (jguillochon@cfa.harvard.edu)
"""

import json
import csv
import re

import astropy.units as u
from astropy.table import Column, Table
from astroquery import log

from . import conf
from ..query import BaseQuery
from ..utils import async_to_sync, commons

__all__ = ['OAC', 'OACClass']

@async_to_sync
class OACClass(BaseQuery):
    """OAC class."""

    URL = conf.server
    TIMEOUT = conf.timeout
    HEADERS = {'Content-type': 'application/json', 'Accept': 'text/plain'}
    FORMAT = None

    def query_object_async(self,
                           event,
                           quantity=None,
                           attribute=None,
                           argument=None,
                           data_format='csv',
                           get_query_payload=False, cache=True):
        """
        Retrieve object(s) asynchronously.

        Query method to retrieve the desired quantities and
        attributes for an object specified by a transient name.

        If no quantities or attributes are given then the query
        returns the top-level metadata about the event(s).

        The complete list of available quantities and attributes
        can be found at https://github.com/astrocatalogs/schema.

        Parameters
        ----------
        event : str or list, required
            Name of the event to query. Can be a list
            of event names.
        quantity : str or list, optional
            Name of quantity to retrieve. Can be
            a list of quantities. The default is None.
        attribute : str or list, optional
            Name of specific attributes to retrieve. Can be a list
            of attributes. The default is None.
        argument : str or list, optional
            These are special conditional arguments that can be applied
            to a query to refine.
            Examples include: 'band=i' returns only i-band photometry,
            'first' returns the first result, 'sortby=attribute' returns
            a table sorted by the given attribute, and 'complete' returns
            only those table rows with all of the requested attributes.
            A complete list of commands and their usage can be found at:
            https://github.com/astrocatalogs/OACAPI. The default is None.
        data_format: str, optional
            Specify the format for the returned data. The default is
            CSV for easy conversion to Astropy Tables. The user can
            also specify JSON which will return a JSON-compliant
            dictionary.
            Note: Not all queries can support CSV output.
        get_query_payload : bool, optional
            When set to `True` the method returns the HTTP request
            parameters as a dict. The actual HTTP request is not made.
            The default value is False.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        request_payload = self._args_to_payload(event,
                                                quantity,
                                                attribute,
                                                argument,
                                                data_format)

        if get_query_payload:
            return request_payload

        response = self._request('GET', self.URL,
                                 data=json.dumps(request_payload),
                                 timeout=self.TIMEOUT,
                                 headers=self.HEADERS,
                                 cache=cache)

        return response

    def query_region_async(self, coordinates,
                           radius=None,
                           height=None, width=None,
                           quantity=None,
                           attribute=None,
                           argument=None,
                           data_format='csv',
                           get_query_payload=False, cache=True):
        """
        Query a region asynchronously.

        Query method to retrieve the desired quantities and
        attributes for an object specified by a region on the sky.
        The search can be either a cone search (using the radius
        parameter) or a box search (using the width/height parameters).

        IMPORTANT: The API can only query a single set of coordinates
        at a time.

        The complete list of available quantities and attributes
        can be found at https://github.com/astrocatalogs/schema.

        Parameters
        ----------
        coordinates : str or `astropy.coordinates`.
            A single set of ra/dec coorindates to query. Can be either
            a list with [ra,dec] or an astropy coordinates object.
            Can be given in sexigesimal or decimal format. The API
            can not query multiple sets of coordinates.
        radius : str, float or `astropy.units.Quantity`, optional
            The radius, in arcseconds, of the cone search centered
            on coordinates. Should be a single, float-convertable value
            or an astropy quantity. The default value is None.
        width : str, float or `astropy.units.Quantity`, optional
            The width, in arcseconds, of the box search centered
            on coordinates. Should be a single, float-convertable value
            or an astropy quantity. The default value is None.
        height : str, float or `astropy.units.Quantity`, optional
            The height, in arcseconds, of the box search centered
            on coordinates. Should be a single, float-convertable value
            or an astropy quantity. The default value is None.
        quantity: str or list, optional
            Name of quantity to retrieve. Can be a
            a list of quantities. The default is None.
        attribute: str or list, optional
            Name of specific attributes to retrieve. Can be a list
            of attributes. The default is None.
        argument : str or list, optional
            These are special conditional arguments that can be applied
            to a query to refine.
            Examples include: 'band=i' returns only i-band photometry,
            'first' returns the first result, 'sortby=attribute' returns
            a table sorted by the given attribute, and 'complete' returns
            only those table rows with all of the requested attributes.
            A complete list of commands and their usage can be found at:
            https://github.com/astrocatalogs/OACAPI. The default is None.
        data_format: str, optional
            Specify the format for the returned data. The default is
            CSV for easy conversion to Astropy Tables. The user can
            also specify JSON which will return a JSON-compliant
            dictionary.
            Note: Not all queries can support CSV output.
        get_query_payload : bool, optional
            When set to `True` the method returns the HTTP request
            parameters as a dict. The actual HTTP request is not made.
            The default value is False.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        # Default object name used for coordinate-based queries
        event = 'catalog'

        request_payload = self._args_to_payload(event,
                                                quantity,
                                                attribute,
                                                argument,
                                                data_format)

        # Check that coordinate object is a valid astropy coordinate object
        # Criteria/Code from ../sdss/core.py
        if (not isinstance(coordinates, list) and
            not isinstance(coordinates, Column) and
            not (isinstance(coordinates, commons.CoordClasses) and
                 not coordinates.isscalar)):

            request_payload['ra'] = coordinates.ra.deg
            request_payload['dec'] = coordinates.dec.deg

        else:
            try:
                request_payload['ra'] = coordinates[0]
                request_payload['dec'] = coordinates[1]
            except IndexError:
                raise IndexError("Please check format of input coordinates.")

        if ((radius is None) and (height is None) and (width is None)):
            raise ValueError("Please enter a radius or width/height pair.")

        if (radius is not None and (height is not None or width is not None)):
            raise ValueError("Please specify ONLY a radius or "
                             "height/width pair.")

        if ((radius is None) and ((height is None) or (width is None))):
            raise ValueError("Please enter both a width and height "
                             "for a box search.")

        # Check that any values are in the proper format.
        # Criteria/Code from ../sdss/core.py
        if radius is not None:
            if isinstance(radius, u.Quantity):
                radius = radius.to(u.arcsec).value
            else:
                try:
                    float(radius)
                except TypeError:
                    raise TypeError("radius should be either Quantity or "
                                    "convertible to float.")

            request_payload['radius'] = radius

        if (width is not None and height is not None):
            if isinstance(width, u.Quantity):
                width = width.to(u.arcsec).value
            else:
                try:
                    float(width)
                except TypeError:
                    raise TypeError("width should be either Quantity or "
                                    "convertible to float.")

            if isinstance(height, u.Quantity):
                height = height.to(u.arcmin).value
            else:
                try:
                    float(height)
                except TypeError:
                    raise TypeError("height should be either Quantity or "
                                    "convertible to float.")

            request_payload['width'] = width
            request_payload['height'] = height

        if get_query_payload:
            return request_payload

        response = self._request('GET', self.URL,
                                 data=json.dumps(request_payload),
                                 timeout=self.TIMEOUT,
                                 headers=self.HEADERS,
                                 cache=cache)

        return response

    def get_photometry_async(self, event, argument=None, cache=True):
        """
        Retrieve all photometry for specified event(s).

        This is a version of the query_object method
        that is set up to quickly return the complete set
        of light curve(s) for the given event(s).

        The light curves are returned by default as an
        Astropy Table.

        Additional arguments can be specified but more complicated
        queries should make use of the base query_object method
        instead of get_photometry.

        Parameters
        ----------
        event : str or list, required
            Name of the event to query. Can be a list
            of event names.
        argument : str or list, optional
            These are special conditional arguments that can be applied
            to a query to refine.
            Examples include: 'band=i' returns only i-band photometry,
            'first' returns the first result, 'sortby=attribute' returns
            a table sorted by the given attribute, and 'complete' returns
            only those table rows with all of the requested attributes.
            A complete list of commands and their usage can be found at:
            https://github.com/astrocatalogs/OACAPI. The default is None.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        response = self.query_object_async(event=event,
                                           quantity='photometry',
                                           attribute=['time', 'magnitude',
                                                      'e_magnitude', 'band',
                                                      'instrument'],
                                           argument=argument,
                                           cache=cache
                                           )

        return response

    def get_single_spectrum_async(self, event, time, cache=True):
        """
        Retrieve a single spectrum at a specified time for given event.

        This is a version of the query_object method
        that is set up to quickly return a single spectrum
        at a user-specified time. The time does not have to be
        precise as the method uses the closest option by default.

        The spectrum is returned as an astropy table.

        More complicated queries, or queries requesting multiple spectra,
        should make use of the base query_object or get_spectra methods.

        Parameters
        ----------
        event : str, required
            Name of the event to query. Must be a single event.
        time : float, required
            A single MJD time to query. This time does not need to be
            exact. The closest spectrum will be returned.

        Returns
        -------
        result : `~astropy.table.Table`
            The default result is an `~astropy.table.Table` object. The
            user can also request a JSON dictionary.
        """
        query_time = 'time=%s' % time
        response = self.query_object_async(event=event,
                                           quantity='spectra',
                                           attribute=['data'],
                                           argument=[query_time, 'closest'],
                                           cache=cache
                                           )

        return response

    def get_spectra_async(self, event, cache=True):
        """
        Retrieve all spectra for a specified event.

        This is a version of the query_object method
        that is set up to quickly return all available spectra
        for a single event.

        The spectra must be returned as a JSON-compliant dictionary.
        Multiple spectra can not be unwrapped into a csv/Table.

        More complicated queries should make use of the
        base query_object methods.

        Parameters
        ----------
        event : str, required
            Name of the event to query. Can be a single event or a
            list of events.

        Returns
        -------
        result : dict
            The default result is a JSON dictionary. An `~astropy.table.Table`
            can not be returned.
        """
        response = self.query_object_async(event=event,
                                           quantity='spectra',
                                           attribute=['time', 'data'],
                                           data_format='json',
                                           cache=cache
                                           )
        return response

    def _args_to_payload(self, event, quantity,
                         attribute, argument, data_format):
        request_payload = dict()

        if (event) and (not isinstance(event, list)):
            event = [event]

        if (quantity) and (not isinstance(quantity, list)):
            quantity = [quantity]

        if (attribute) and (not isinstance(attribute, list)):
            attribute = [attribute]

        if (argument) and (not isinstance(argument, list)):
            argument = [argument]

        request_payload['event'] = event
        request_payload['quantity'] = quantity
        request_payload['attribute'] = attribute

        if argument:
            if 'format' in argument:
                raise KeyError("Please specify the output format using the "
                               "data_format function argument")
            if 'radius' in argument:
                raise KeyError("A search radius should be specified "
                               "explicitly using the query_region method.")
            if 'width' in argument:
                raise KeyError("A search width should be specified "
                               "explicitly using the query_region method.")
            if 'height' in argument:
                raise KeyError("A search height should be specified "
                               "explicitly using the query_region method.")

            for arg in argument:
                if '=' in arg:
                    split_arg = arg.split('=')
                    request_payload[split_arg[0]] = split_arg[1]
                else:
                    request_payload[arg] = True

        if ((data_format.lower() == 'csv') or
                (data_format.lower() == 'json')):
            request_payload['format'] = data_format.lower()
        else:
            raise ValueError("The format must be either csv or JSON.")

        self.FORMAT = data_format.lower()

        return request_payload

    def _format_output(self, raw_output):
        if self.FORMAT == 'csv':

            fixed_raw_output = re.sub('<[^<]+?>', '', raw_output)
            split_output = fixed_raw_output.splitlines()

            # Remove any HTML tags

            columns = list(csv.reader([split_output[0]], delimiter=',',
                           quotechar='"'))[0]
            rows = split_output[1:]

            # Quick test to see if API returned a valid csv file
            # If not, try to return JSON-compliant dictionary.
            test_row = list(csv.reader([rows[0]], delimiter=',',
                            quotechar='"'))[0]

            if (len(columns) != len(test_row)):
                log.info("The API did not return a valid CSV output! \n"
                      "Outputing JSON-compliant dictionary instead.")

                output = json.loads(raw_output)
                return output

            # Initialize and populate dictionary
            output_dict = {key: [] for key in columns}

            for row in rows:

                split_row = list(csv.reader([row], delimiter=',',
                                 quotechar='"'))[0]

                for ct, key in enumerate(columns):
                    output_dict[key].append(split_row[ct])

            # Convert dictionary to Astropy Table.
            output = Table(output_dict, names=columns)

        else:
            # Server response is JSON compliant. Simply
            # convert from raw text to dictionary.
            output = json.loads(raw_output)

        return output

    def _parse_result(self, response, verbose=False):
        if not verbose:
            commons.suppress_vo_warnings()

        if response.status_code != 200:
            raise AttributeError("ERROR: The web service returned error code: %s" %
                response.status_code)

        if 'message' in response.text:
            raise KeyError("ERROR: API Server returned the following error:\n{}".format(response.text))

        raw_output = response.text
        output_response = self._format_output(raw_output)

        return output_response

OAC = OACClass()
<|endoftext|>


<commit_before>import os

import sublime
from sublime_plugin import WindowCommand, TextCommand
from sublime_plugin import EventListener

from . import intra_line_colorizer
from ..git_command import GitCommand
from ..runtime import enqueue_on_worker
from ..utils import focus_view
from ..view import replace_view_content
from ...common import util
from ...core.settings import SettingsMixin

__all__ = (
    "gs_commit",
    "gs_prepare_commit_refresh_diff",
    "gs_commit_view_do_commit",
    "gs_commit_view_sign",
    "gs_commit_view_close",
    "GsPrepareCommitFocusEventListener",
    "GsPedanticEnforceEventListener",
)

MYPY = False
if MYPY:
    from typing import Optional, Tuple

COMMIT_HELP_TEXT_EXTRA = """##
## You may also reference or close a GitHub issue with this commit.
## To do so, type `#` followed by the `tab` key.  You will be shown a
## list of issues related to the current repo.  You may also type
## `owner/repo#` plus the `tab` key to reference an issue in a
## different GitHub repo.
"""

COMMIT_HELP_TEXT_ALT = """

## To make a commit, type your commit message and close the window.
## To cancel the commit, delete the commit message and close the window.
## To sign off on the commit, press {key}-S.
""".format(key=util.super_key) + COMMIT_HELP_TEXT_EXTRA

COMMIT_HELP_TEXT = """

## To make a commit, type your commit message and press {key}-ENTER.
## To cancel the commit, close the window. To sign off on the commit,
## press {key}-S.
""".format(key=util.super_key) + COMMIT_HELP_TEXT_EXTRA

COMMIT_SIGN_TEXT = """

Signed-off-by: {name} <{email}>
"""

COMMIT_TITLE = "COMMIT: {}"

CONFIRM_ABORT = "Confirm to abort commit?"

def compute_identifier_for_view(view):
    # type: (sublime.View) -> Optional[Tuple]
    settings = view.settings()
    return (
        settings.get('git_savvy.repo_path'),
    ) if settings.get('git_savvy.commit_view') else None

class gs_commit(WindowCommand, GitCommand):

    """
    Display a transient window to capture the user's desired commit message.
    If the user is amending the previous commit, pre-populate the commit
    message area with the previous commit message.
    """

    def run(self, repo_path=None, include_unstaged=False, amend=False):
        repo_path = repo_path or self.repo_path

        this_id = (
            repo_path,
        )
        for view in self.window.views():
            if compute_identifier_for_view(view) == this_id:
                focus_view(view)
                break
        else:
            view = self.window.new_file()
            settings = view.settings()
            settings.set("git_savvy.repo_path", repo_path)
            settings.set("git_savvy.get_long_text_view", True)
            settings.set("git_savvy.commit_view", True)
            settings.set("git_savvy.commit_view.include_unstaged", include_unstaged)
            settings.set("git_savvy.commit_view.amend", amend)
            commit_on_close = self.savvy_settings.get("commit_on_close")
            settings.set("git_savvy.commit_on_close", commit_on_close)
            prompt_on_abort_commit = self.savvy_settings.get("prompt_on_abort_commit")
            settings.set("git_savvy.prompt_on_abort_commit", prompt_on_abort_commit)

            view.set_syntax_file("Packages/GitSavvy/syntax/make_commit.sublime-syntax")
            view.run_command("gs_handle_vintageous")

            title = COMMIT_TITLE.format(os.path.basename(repo_path))
            view.set_name(title)
            view.set_scratch(True)  # ignore dirty on actual commit
            self.initialize_view(view, include_unstaged, amend)

    def initialize_view(self, view, include_unstaged, amend):
        # type: (sublime.View, bool, bool) -> None
        merge_msg_path = os.path.join(self.repo_path, ".git", "MERGE_MSG")

        help_text = (
            COMMIT_HELP_TEXT_ALT
            if self.savvy_settings.get("commit_on_close")
            else COMMIT_HELP_TEXT
        )

        if amend:
            last_commit_message = self.git("log", "-1", "--pretty=%B").strip()
            initial_text = last_commit_message + help_text
        elif os.path.exists(merge_msg_path):
            with util.file.safe_open(merge_msg_path, "r") as f:
                initial_text = f.read() + help_text
        else:
            initial_text = help_text

        commit_help_extra_file = self.savvy_settings.get("commit_help_extra_file") or ".commit_help"
        commit_help_extra_path = os.path.join(self.repo_path, commit_help_extra_file)
        if os.path.exists(commit_help_extra_path):
            with util.file.safe_open(commit_help_extra_path, "r", encoding="utf-8") as f:
                initial_text += f.read()

        replace_view_content(view, initial_text)
        view.run_command("gs_prepare_commit_refresh_diff")

class gs_prepare_commit_refresh_diff(TextCommand, GitCommand):
    def run(self, edit, sync=True):
        # type: (sublime.Edit, bool) -> None
        if sync:
            self.run_impl()
        else:
            enqueue_on_worker(self.run_impl)

    def run_impl(self):
        # type: () -> None
        view = self.view
        settings = view.settings()
        include_unstaged = settings.get("git_savvy.commit_view.include_unstaged")
        amend = settings.get("git_savvy.commit_view.amend")
        show_commit_diff = self.savvy_settings.get("show_commit_diff")
        # for backward compatibility, check also if show_commit_diff is True
        shows_diff = show_commit_diff is True or show_commit_diff == "full"
        shows_stat = (
            show_commit_diff == "stat"
            or (show_commit_diff == "full" and self.savvy_settings.get("show_diffstat"))
        )
        diff_text = self.git(
            "diff",
            "--no-color",
            "--patch" if shows_diff else None,
            "--stat" if shows_stat else None,
            "--cached" if not include_unstaged else None,
            "HEAD^" if amend
            else "HEAD" if include_unstaged
            else None
        )
        if diff_text:
            final_text = ("\n" + diff_text) if shows_diff or shows_stat else ""
        else:
            final_text = "\nNothing to commit.\n"

        try:
            region = view.find_by_selector("git-savvy.diff")[0]
        except IndexError:
            region = sublime.Region(view.size())

        replace_view_content(view, final_text, region)
        if shows_diff:
            intra_line_colorizer.annotate_intra_line_differences(view, final_text, region.begin())

class GsPrepareCommitFocusEventListener(EventListener):
    def on_activated(self, view):
        if view.settings().get("git_savvy.commit_view"):
            view.run_command("gs_prepare_commit_refresh_diff", {"sync": False})

class GsPedanticEnforceEventListener(EventListener, SettingsMixin):
    """
    Set regions to warn for pedantic commits
    """

    def on_selection_modified(self, view):
        if 'make_commit' not in view.settings().get('syntax', ''):
            return

        if not self.savvy_settings.get('pedantic_commit'):
            return

        self.view = view
        self.first_line_limit = self.savvy_settings.get('pedantic_commit_first_line_length')
        self.body_line_limit = self.savvy_settings.get('pedantic_commit_message_line_length')
        self.warning_length = self.savvy_settings.get('pedantic_commit_warning_length')

        self.comment_start_region = self.view.find_by_selector("meta.dropped.git.commit")
        self.first_comment_line = None
        if self.comment_start_region:
            self.first_comment_line = self.view.rowcol(self.comment_start_region[0].begin())[0]

        if self.savvy_settings.get('pedantic_commit_ruler'):
            self.view.settings().set("rulers", self.find_rulers())

        warning, illegal = self.find_too_long_lines()
        self.view.add_regions(
            'make_commit_warning', warning,
            scope='invalid.deprecated.line-too-long.git-commit', flags=sublime.DRAW_NO_FILL)
        self.view.add_regions(
            'make_commit_illegal', illegal,
            scope='invalid.deprecated.line-too-long.git-commit')

    def find_rulers(self):
        on_first_line = False
        on_message_body = False

        subject_near_limit = len(self.view.substr(self.view.line(sublime.Region(0))).rstrip()) >= 40

        for region in self.view.sel():
            first_line = self.view.rowcol(region.begin())[0]
            last_line = self.view.rowcol(region.end())[0]

            if first_line == 0 and subject_near_limit:
                on_first_line = True

            if self.first_comment_line:
                if first_line in range(2, self.first_comment_line) or last_line in range(2, self.first_comment_line):
                    on_message_body = True
            else:
                if first_line >= 2 or last_line >= 2:
                    on_message_body = True

        new_rulers = []
        if on_first_line:
            new_rulers.append(self.first_line_limit)

        if on_message_body:
            new_rulers.append(self.body_line_limit)

        return new_rulers

    def find_too_long_lines(self):
        warning_lines = []
        illegal_lines = []

        first_line = self.view.line(sublime.Region(0, 0))
        length = len(self.view.substr(first_line).rstrip())
        if length > self.first_line_limit:
            warning_lines.append(sublime.Region(
                first_line.a + self.first_line_limit,
                min(first_line.a + self.first_line_limit + self.warning_length, first_line.b)))

        if length > self.first_line_limit + self.warning_length:
            illegal_lines.append(
                sublime.Region(first_line.a + self.first_line_limit + self.warning_length, first_line.b))

        # Add second line to illegal
        if self.first_comment_line is None or self.first_comment_line > 1:
            illegal_lines.append(sublime.Region(self.view.text_point(1, 0), self.view.text_point(2, 0) - 1))

        if self.first_comment_line:
            body_region = sublime.Region(self.view.text_point(2, 0), self.comment_start_region[0].begin())
        else:
            body_region = sublime.Region(self.view.text_point(2, 0), self.view.size())

        for line in self.view.lines(body_region):
            length = line.b - line.a
            if length > self.body_line_limit:
                warning_lines.append(sublime.Region(
                    line.a + self.body_line_limit,
                    min(line.a + self.body_line_limit + self.warning_length, line.b)))

            if self.body_line_limit + self.warning_length < length:
                illegal_lines.append(sublime.Region(line.a + self.body_line_limit + self.warning_length, line.b))

        return [warning_lines, illegal_lines]

def extract_commit_message(view):
    # type: (sublime.View) -> str
    try:
        region = view.find_by_selector("meta.commit.message")[0]
    except IndexError:
        return ""

    return view.substr(region)

class gs_commit_view_do_commit(TextCommand, GitCommand):

    """
    Take the text of the current view (minus the help message text) and
    make a commit using the text for the commit message.
    """

    def run(self, edit, message=None):
        enqueue_on_worker(self.run_impl, message)

    def run_impl(self, commit_message=None):
        window = self.view.window()
        if not window:
            return

        settings = self.view.settings()
        if settings.get("git_savvy.commit_view.is_commiting", False):
            return

        if commit_message is None:
            commit_message = extract_commit_message(self.view)

        settings.set("git_savvy.commit_view.is_commiting", True)
        window.status_message("Commiting...")
        try:
            self.git(
                "commit",
                "-a" if settings.get("git_savvy.commit_view.include_unstaged") else None,
                "--amend" if settings.get("git_savvy.commit_view.amend") else None,
                "-F",
                "-",
                stdin=commit_message
            )
        finally:
            settings.set("git_savvy.commit_view.is_commiting", False)

        window.status_message("Committed successfully.")

        self.view.close()
        util.view.refresh_gitsavvy_interfaces(window)

class gs_commit_view_sign(TextCommand, GitCommand):

    """
    Sign off on the commit with full name and email.
    """

    def run(self, edit):
        config_name = self.git("config", "user.name").strip()
        config_email = self.git("config", "user.email").strip()
        commit_message = extract_commit_message(self.view)

        sign_text = COMMIT_SIGN_TEXT.format(name=config_name, email=config_email)
        new_commit_message = commit_message.rstrip() + sign_text + "\n"

        view_text = self.view.substr(sublime.Region(0, self.view.size()))
        new_view_text = new_commit_message + view_text[len(commit_message):]
        replace_view_content(self.view, new_view_text)

class gs_commit_view_close(TextCommand, GitCommand):

    """
    Perform commit action on commit view close if `commit_on_close` setting
    is enabled.
    """

    def run(self, edit):
        if self.view.settings().get("git_savvy.commit_on_close"):
            message_txt = extract_commit_message(self.view).strip()
            if message_txt:
                self.view.run_command("gs_commit_view_do_commit", {"message": message_txt})
            else:
                self.view.close()

        elif self.view.settings().get("git_savvy.prompt_on_abort_commit"):
            message_txt = extract_commit_message(self.view).strip()
            if not message_txt or sublime.ok_cancel_dialog(CONFIRM_ABORT):
                self.view.close()

        else:
            self.view.close()
<commit_msg>Fix: Update settings when reusing commit message view<commit_after>import os

import sublime
from sublime_plugin import WindowCommand, TextCommand
from sublime_plugin import EventListener

from . import intra_line_colorizer
from ..git_command import GitCommand
from ..runtime import enqueue_on_worker
from ..utils import focus_view
from ..view import replace_view_content
from ...common import util
from ...core.settings import SettingsMixin

__all__ = (
    "gs_commit",
    "gs_prepare_commit_refresh_diff",
    "gs_commit_view_do_commit",
    "gs_commit_view_sign",
    "gs_commit_view_close",
    "GsPrepareCommitFocusEventListener",
    "GsPedanticEnforceEventListener",
)

MYPY = False
if MYPY:
    from typing import Optional, Tuple

COMMIT_HELP_TEXT_EXTRA = """##
## You may also reference or close a GitHub issue with this commit.
## To do so, type `#` followed by the `tab` key.  You will be shown a
## list of issues related to the current repo.  You may also type
## `owner/repo#` plus the `tab` key to reference an issue in a
## different GitHub repo.
"""

COMMIT_HELP_TEXT_ALT = """

## To make a commit, type your commit message and close the window.
## To cancel the commit, delete the commit message and close the window.
## To sign off on the commit, press {key}-S.
""".format(key=util.super_key) + COMMIT_HELP_TEXT_EXTRA

COMMIT_HELP_TEXT = """

## To make a commit, type your commit message and press {key}-ENTER.
## To cancel the commit, close the window. To sign off on the commit,
## press {key}-S.
""".format(key=util.super_key) + COMMIT_HELP_TEXT_EXTRA

COMMIT_SIGN_TEXT = """

Signed-off-by: {name} <{email}>
"""

COMMIT_TITLE = "COMMIT: {}"

CONFIRM_ABORT = "Confirm to abort commit?"

def compute_identifier_for_view(view):
    # type: (sublime.View) -> Optional[Tuple]
    settings = view.settings()
    return (
        settings.get('git_savvy.repo_path'),
    ) if settings.get('git_savvy.commit_view') else None

class gs_commit(WindowCommand, GitCommand):

    """
    Display a transient window to capture the user's desired commit message.
    If the user is amending the previous commit, pre-populate the commit
    message area with the previous commit message.
    """

    def run(self, repo_path=None, include_unstaged=False, amend=False):
        repo_path = repo_path or self.repo_path

        this_id = (
            repo_path,
        )
        for view in self.window.views():
            if compute_identifier_for_view(view) == this_id:
                settings = view.settings()
                settings.set("git_savvy.commit_view.include_unstaged", include_unstaged)
                settings.set("git_savvy.commit_view.amend", amend)
                focus_view(view)
                break
        else:
            view = self.window.new_file()
            settings = view.settings()
            settings.set("git_savvy.repo_path", repo_path)
            settings.set("git_savvy.get_long_text_view", True)
            settings.set("git_savvy.commit_view", True)
            settings.set("git_savvy.commit_view.include_unstaged", include_unstaged)
            settings.set("git_savvy.commit_view.amend", amend)
            commit_on_close = self.savvy_settings.get("commit_on_close")
            settings.set("git_savvy.commit_on_close", commit_on_close)
            prompt_on_abort_commit = self.savvy_settings.get("prompt_on_abort_commit")
            settings.set("git_savvy.prompt_on_abort_commit", prompt_on_abort_commit)

            view.set_syntax_file("Packages/GitSavvy/syntax/make_commit.sublime-syntax")
            view.run_command("gs_handle_vintageous")

            title = COMMIT_TITLE.format(os.path.basename(repo_path))
            view.set_name(title)
            view.set_scratch(True)  # ignore dirty on actual commit
            self.initialize_view(view, include_unstaged, amend)

    def initialize_view(self, view, include_unstaged, amend):
        # type: (sublime.View, bool, bool) -> None
        merge_msg_path = os.path.join(self.repo_path, ".git", "MERGE_MSG")

        help_text = (
            COMMIT_HELP_TEXT_ALT
            if self.savvy_settings.get("commit_on_close")
            else COMMIT_HELP_TEXT
        )

        if amend:
            last_commit_message = self.git("log", "-1", "--pretty=%B").strip()
            initial_text = last_commit_message + help_text
        elif os.path.exists(merge_msg_path):
            with util.file.safe_open(merge_msg_path, "r") as f:
                initial_text = f.read() + help_text
        else:
            initial_text = help_text

        commit_help_extra_file = self.savvy_settings.get("commit_help_extra_file") or ".commit_help"
        commit_help_extra_path = os.path.join(self.repo_path, commit_help_extra_file)
        if os.path.exists(commit_help_extra_path):
            with util.file.safe_open(commit_help_extra_path, "r", encoding="utf-8") as f:
                initial_text += f.read()

        replace_view_content(view, initial_text)
        view.run_command("gs_prepare_commit_refresh_diff")

class gs_prepare_commit_refresh_diff(TextCommand, GitCommand):
    def run(self, edit, sync=True):
        # type: (sublime.Edit, bool) -> None
        if sync:
            self.run_impl()
        else:
            enqueue_on_worker(self.run_impl)

    def run_impl(self):
        # type: () -> None
        view = self.view
        settings = view.settings()
        include_unstaged = settings.get("git_savvy.commit_view.include_unstaged")
        amend = settings.get("git_savvy.commit_view.amend")
        show_commit_diff = self.savvy_settings.get("show_commit_diff")
        # for backward compatibility, check also if show_commit_diff is True
        shows_diff = show_commit_diff is True or show_commit_diff == "full"
        shows_stat = (
            show_commit_diff == "stat"
            or (show_commit_diff == "full" and self.savvy_settings.get("show_diffstat"))
        )
        diff_text = self.git(
            "diff",
            "--no-color",
            "--patch" if shows_diff else None,
            "--stat" if shows_stat else None,
            "--cached" if not include_unstaged else None,
            "HEAD^" if amend
            else "HEAD" if include_unstaged
            else None
        )
        if diff_text:
            final_text = ("\n" + diff_text) if shows_diff or shows_stat else ""
        else:
            final_text = "\nNothing to commit.\n"

        try:
            region = view.find_by_selector("git-savvy.diff")[0]
        except IndexError:
            region = sublime.Region(view.size())

        replace_view_content(view, final_text, region)
        if shows_diff:
            intra_line_colorizer.annotate_intra_line_differences(view, final_text, region.begin())

class GsPrepareCommitFocusEventListener(EventListener):
    def on_activated(self, view):
        if view.settings().get("git_savvy.commit_view"):
            view.run_command("gs_prepare_commit_refresh_diff", {"sync": False})

class GsPedanticEnforceEventListener(EventListener, SettingsMixin):
    """
    Set regions to warn for pedantic commits
    """

    def on_selection_modified(self, view):
        if 'make_commit' not in view.settings().get('syntax', ''):
            return

        if not self.savvy_settings.get('pedantic_commit'):
            return

        self.view = view
        self.first_line_limit = self.savvy_settings.get('pedantic_commit_first_line_length')
        self.body_line_limit = self.savvy_settings.get('pedantic_commit_message_line_length')
        self.warning_length = self.savvy_settings.get('pedantic_commit_warning_length')

        self.comment_start_region = self.view.find_by_selector("meta.dropped.git.commit")
        self.first_comment_line = None
        if self.comment_start_region:
            self.first_comment_line = self.view.rowcol(self.comment_start_region[0].begin())[0]

        if self.savvy_settings.get('pedantic_commit_ruler'):
            self.view.settings().set("rulers", self.find_rulers())

        warning, illegal = self.find_too_long_lines()
        self.view.add_regions(
            'make_commit_warning', warning,
            scope='invalid.deprecated.line-too-long.git-commit', flags=sublime.DRAW_NO_FILL)
        self.view.add_regions(
            'make_commit_illegal', illegal,
            scope='invalid.deprecated.line-too-long.git-commit')

    def find_rulers(self):
        on_first_line = False
        on_message_body = False

        subject_near_limit = len(self.view.substr(self.view.line(sublime.Region(0))).rstrip()) >= 40

        for region in self.view.sel():
            first_line = self.view.rowcol(region.begin())[0]
            last_line = self.view.rowcol(region.end())[0]

            if first_line == 0 and subject_near_limit:
                on_first_line = True

            if self.first_comment_line:
                if first_line in range(2, self.first_comment_line) or last_line in range(2, self.first_comment_line):
                    on_message_body = True
            else:
                if first_line >= 2 or last_line >= 2:
                    on_message_body = True

        new_rulers = []
        if on_first_line:
            new_rulers.append(self.first_line_limit)

        if on_message_body:
            new_rulers.append(self.body_line_limit)

        return new_rulers

    def find_too_long_lines(self):
        warning_lines = []
        illegal_lines = []

        first_line = self.view.line(sublime.Region(0, 0))
        length = len(self.view.substr(first_line).rstrip())
        if length > self.first_line_limit:
            warning_lines.append(sublime.Region(
                first_line.a + self.first_line_limit,
                min(first_line.a + self.first_line_limit + self.warning_length, first_line.b)))

        if length > self.first_line_limit + self.warning_length:
            illegal_lines.append(
                sublime.Region(first_line.a + self.first_line_limit + self.warning_length, first_line.b))

        # Add second line to illegal
        if self.first_comment_line is None or self.first_comment_line > 1:
            illegal_lines.append(sublime.Region(self.view.text_point(1, 0), self.view.text_point(2, 0) - 1))

        if self.first_comment_line:
            body_region = sublime.Region(self.view.text_point(2, 0), self.comment_start_region[0].begin())
        else:
            body_region = sublime.Region(self.view.text_point(2, 0), self.view.size())

        for line in self.view.lines(body_region):
            length = line.b - line.a
            if length > self.body_line_limit:
                warning_lines.append(sublime.Region(
                    line.a + self.body_line_limit,
                    min(line.a + self.body_line_limit + self.warning_length, line.b)))

            if self.body_line_limit + self.warning_length < length:
                illegal_lines.append(sublime.Region(line.a + self.body_line_limit + self.warning_length, line.b))

        return [warning_lines, illegal_lines]

def extract_commit_message(view):
    # type: (sublime.View) -> str
    try:
        region = view.find_by_selector("meta.commit.message")[0]
    except IndexError:
        return ""

    return view.substr(region)

class gs_commit_view_do_commit(TextCommand, GitCommand):

    """
    Take the text of the current view (minus the help message text) and
    make a commit using the text for the commit message.
    """

    def run(self, edit, message=None):
        enqueue_on_worker(self.run_impl, message)

    def run_impl(self, commit_message=None):
        window = self.view.window()
        if not window:
            return

        settings = self.view.settings()
        if settings.get("git_savvy.commit_view.is_commiting", False):
            return

        if commit_message is None:
            commit_message = extract_commit_message(self.view)

        settings.set("git_savvy.commit_view.is_commiting", True)
        window.status_message("Commiting...")
        try:
            self.git(
                "commit",
                "-a" if settings.get("git_savvy.commit_view.include_unstaged") else None,
                "--amend" if settings.get("git_savvy.commit_view.amend") else None,
                "-F",
                "-",
                stdin=commit_message
            )
        finally:
            settings.set("git_savvy.commit_view.is_commiting", False)

        window.status_message("Committed successfully.")

        self.view.close()
        util.view.refresh_gitsavvy_interfaces(window)

class gs_commit_view_sign(TextCommand, GitCommand):

    """
    Sign off on the commit with full name and email.
    """

    def run(self, edit):
        config_name = self.git("config", "user.name").strip()
        config_email = self.git("config", "user.email").strip()
        commit_message = extract_commit_message(self.view)

        sign_text = COMMIT_SIGN_TEXT.format(name=config_name, email=config_email)
        new_commit_message = commit_message.rstrip() + sign_text + "\n"

        view_text = self.view.substr(sublime.Region(0, self.view.size()))
        new_view_text = new_commit_message + view_text[len(commit_message):]
        replace_view_content(self.view, new_view_text)

class gs_commit_view_close(TextCommand, GitCommand):

    """
    Perform commit action on commit view close if `commit_on_close` setting
    is enabled.
    """

    def run(self, edit):
        if self.view.settings().get("git_savvy.commit_on_close"):
            message_txt = extract_commit_message(self.view).strip()
            if message_txt:
                self.view.run_command("gs_commit_view_do_commit", {"message": message_txt})
            else:
                self.view.close()

        elif self.view.settings().get("git_savvy.prompt_on_abort_commit"):
            message_txt = extract_commit_message(self.view).strip()
            if not message_txt or sublime.ok_cancel_dialog(CONFIRM_ABORT):
                self.view.close()

        else:
            self.view.close()
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys

from setuptools import setup, find_packages

APP_NAME = 'tarbell'
VERSION = '1.0'

settings = dict()

# Publish Helper.
if sys.argv[-1] == 'publish':
    os.system('python setup.py sdist upload')
    sys.exit()

settings.update(
    name=APP_NAME,
    version=VERSION,
    author=u'Chicago Tribune News Applications Team',
    author_email='newsapps@tribune.com',
    url='http://github.com/newsapps/flask-tarbell',
    license='MIT',
    description='A very simple content management system',
    long_description='',
    zip_safe=False,
    packages=find_packages(),
    include_package_data=True,
    install_requires=[
        "Flask==0.10.1",
        "Jinja2==2.7.3",
        "Markdown==2.4.1",
        "MarkupSafe==0.23",
        "PyYAML==3.11",
        "boto==2.34.0",
        "clint==0.4.1",
        "gnureadline==6.3.3",
        "google-api-python-client==1.3.1",
        "keyring==4.0",
        "python-dateutil>=2.2",
        "requests==2.3.0",
        "sh==1.09",
        "wsgiref==0.1.2",
        "xlrd==0.9.3",
    ],
    entry_points={
        'console_scripts': [
            'tarbell = tarbell.cli:main',
        ],
    },
    keywords=['Development Status :: 5 - Production/Stable',
          'License :: OSI Approved :: MIT License',
          'Operating System :: OS Independent',
          'Programming Language :: Python',
          'Topic :: Internet',
          ],
)

setup(**settings)
<commit_msg>make setup.py executable<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys

from setuptools import setup, find_packages

APP_NAME = 'tarbell'
VERSION = '1.0'

settings = dict()

# Publish Helper.
if sys.argv[-1] == 'publish':
    os.system('python setup.py sdist upload')
    sys.exit()

settings.update(
    name=APP_NAME,
    version=VERSION,
    author=u'Chicago Tribune News Applications Team',
    author_email='newsapps@tribune.com',
    url='http://github.com/newsapps/flask-tarbell',
    license='MIT',
    description='A very simple content management system',
    long_description='',
    zip_safe=False,
    packages=find_packages(),
    include_package_data=True,
    install_requires=[
        "Flask==0.10.1",
        "Jinja2==2.7.3",
        "Markdown==2.4.1",
        "MarkupSafe==0.23",
        "PyYAML==3.11",
        "boto==2.34.0",
        "clint==0.4.1",
        "gnureadline==6.3.3",
        "google-api-python-client==1.3.1",
        "keyring==4.0",
        "python-dateutil>=2.2",
        "requests==2.3.0",
        "sh==1.09",
        "wsgiref==0.1.2",
        "xlrd==0.9.3",
    ],
    entry_points={
        'console_scripts': [
            'tarbell = tarbell.cli:main',
        ],
    },
    keywords=['Development Status :: 5 - Production/Stable',
          'License :: OSI Approved :: MIT License',
          'Operating System :: OS Independent',
          'Programming Language :: Python',
          'Topic :: Internet',
          ],
)

setup(**settings)
<|endoftext|>


<commit_before>########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
############

from setuptools import setup

setup(
    name='cloudify-amqp-influxdb',
    version='5.0.dev1',
    author='Cloudify',
    author_email='cosmo-admin@gigaspaces.com',
    packages=['amqp_influxdb'],
    entry_points={
        'console_scripts': [
            'cloudify-amqp-influxdb = amqp_influxdb.__main__:main',
        ]
    },
    install_requires=[
        'pika==0.9.14',
        'requests==2.7.0'
    ],
)
<commit_msg>Bump version<commit_after>########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
############

from setuptools import setup

setup(
    name='cloudify-amqp-influxdb',
    version='4.6',
    author='Cloudify',
    author_email='cosmo-admin@gigaspaces.com',
    packages=['amqp_influxdb'],
    entry_points={
        'console_scripts': [
            'cloudify-amqp-influxdb = amqp_influxdb.__main__:main',
        ]
    },
    install_requires=[
        'pika==0.9.14',
        'requests==2.7.0'
    ],
)
<|endoftext|>


<commit_before>import itertools
import os
import json
from collections import OrderedDict, defaultdict
import sqlparse

import dbt.utils
import dbt.include
import dbt.tracking

from dbt import deprecations
from dbt.utils import get_materialization, NodeType, is_type
from dbt.linker import Linker

import dbt.compat
import dbt.context.runtime
import dbt.contracts.project
import dbt.exceptions
import dbt.flags
import dbt.loader
import dbt.config
from dbt.contracts.graph.compiled import CompiledNode, CompiledGraph

from dbt.clients.system import write_json
from dbt.logger import GLOBAL_LOGGER as logger

graph_file_name = 'graph.gpickle'

def print_compile_stats(stats):
    names = {
        NodeType.Model: 'models',
        NodeType.Test: 'tests',
        NodeType.Archive: 'archives',
        NodeType.Analysis: 'analyses',
        NodeType.Macro: 'macros',
        NodeType.Operation: 'operations',
        NodeType.Seed: 'seed files',
    }

    results = {k: 0 for k in names.keys()}
    results.update(stats)

    stat_line = ", ".join(
        ["{} {}".format(ct, names.get(t)) for t, ct in results.items()])

    logger.info("Found {}".format(stat_line))

def _add_prepended_cte(prepended_ctes, new_cte):
    for dct in prepended_ctes:
        if dct['id'] == new_cte['id']:
            dct['sql'] = new_cte['sql']
            return
    prepended_ctes.append(new_cte)

def _extend_prepended_ctes(prepended_ctes, new_prepended_ctes):
    for new_cte in new_prepended_ctes:
        _add_prepended_cte(prepended_ctes, new_cte)

def prepend_ctes(model, manifest):
    model, _, manifest = recursively_prepend_ctes(model, manifest)

    return (model, manifest)

def recursively_prepend_ctes(model, manifest):
    if model.extra_ctes_injected:
        return (model, model.extra_ctes, manifest)

    if dbt.flags.STRICT_MODE:
        # ensure that the cte we're adding to is compiled
        CompiledNode(**model.serialize())

    prepended_ctes = []

    for cte in model.extra_ctes:
        cte_id = cte['id']
        cte_to_add = manifest.nodes.get(cte_id)
        cte_to_add, new_prepended_ctes, manifest = recursively_prepend_ctes(
            cte_to_add, manifest)
        _extend_prepended_ctes(prepended_ctes, new_prepended_ctes)
        new_cte_name = '__dbt__CTE__{}'.format(cte_to_add.get('name'))
        sql = ' {} as (\n{}\n)'.format(new_cte_name, cte_to_add.compiled_sql)
        _add_prepended_cte(prepended_ctes, {'id': cte_id, 'sql': sql})

    model.prepend_ctes(prepended_ctes)

    manifest.nodes[model.unique_id] = model

    return (model, prepended_ctes, manifest)

class Compiler(object):
    def __init__(self, config):
        self.config = config

    def initialize(self):
        dbt.clients.system.make_directory(self.config.target_path)
        dbt.clients.system.make_directory(self.config.modules_path)

    def compile_node(self, node, manifest, extra_context=None):
        if extra_context is None:
            extra_context = {}

        logger.debug("Compiling {}".format(node.get('unique_id')))

        data = node.to_dict()
        data.update({
            'compiled': False,
            'compiled_sql': None,
            'extra_ctes_injected': False,
            'extra_ctes': [],
            'injected_sql': None,
        })
        compiled_node = CompiledNode(**data)

        context = dbt.context.runtime.generate(
            compiled_node, self.config, manifest)
        context.update(extra_context)

        compiled_node.compiled_sql = dbt.clients.jinja.get_rendered(
            node.get('raw_sql'),
            context,
            node)

        compiled_node.compiled = True

        injected_node, _ = prepend_ctes(compiled_node, manifest)

        should_wrap = {NodeType.Test, NodeType.Operation}
        if injected_node.resource_type in should_wrap:
            # data tests get wrapped in count(*)
            # TODO : move this somewhere more reasonable
            if 'data' in injected_node.tags and \
               is_type(injected_node, NodeType.Test):
                injected_node.wrapped_sql = (
                    "select count(*) from (\n{test_sql}\n) sbq").format(
                        test_sql=injected_node.injected_sql)
            else:
                # don't wrap schema tests or analyses.
                injected_node.wrapped_sql = injected_node.injected_sql

        elif is_type(injected_node, NodeType.Archive):
            # unfortunately we do everything automagically for
            # archives. in the future it'd be nice to generate
            # the SQL at the parser level.
            pass

        elif(is_type(injected_node, NodeType.Model) and
             get_materialization(injected_node) == 'ephemeral'):
            pass

        else:
            injected_node.wrapped_sql = None

        return injected_node

    def write_graph_file(self, linker, manifest):
        filename = graph_file_name
        graph_path = os.path.join(self.config.target_path, filename)
        linker.write_graph(graph_path, manifest)

    def link_node(self, linker, node, manifest):
        linker.add_node(node.unique_id)

        for dependency in node.depends_on_nodes:
            if manifest.nodes.get(dependency):
                linker.dependency(
                    node.unique_id,
                    (manifest.nodes.get(dependency).unique_id))
            else:
                dbt.exceptions.dependency_not_found(node, dependency)

    def link_graph(self, linker, manifest):
        for node in manifest.nodes.values():
            self.link_node(linker, node, manifest)

        cycle = linker.find_cycles()

        if cycle:
            raise RuntimeError("Found a cycle: {}".format(cycle))

    def compile(self, manifest):
        linker = Linker()

        self.link_graph(linker, manifest)

        stats = defaultdict(int)

        for node_name, node in itertools.chain(
                manifest.nodes.items(),
                manifest.macros.items()):
            stats[node.resource_type] += 1

        self.write_graph_file(linker, manifest)
        print_compile_stats(stats)

        return linker

def compile_manifest(config, manifest):
    compiler = Compiler(config)
    compiler.initialize()
    return compiler.compile(manifest)

def compile_node(adapter, config, node, manifest, extra_context):
    compiler = Compiler(config)
    node = compiler.compile_node(node, manifest, extra_context)
    node = _inject_runtime_config(adapter, node, extra_context)

    if(node.injected_sql is not None and
       not (dbt.utils.is_type(node, NodeType.Archive))):
        logger.debug('Writing injected SQL for node "{}"'.format(
            node.unique_id))

        written_path = dbt.writer.write_node(
            node,
            config.target_path,
            'compiled',
            node.injected_sql)

        node.build_path = written_path

    return node

def _inject_runtime_config(adapter, node, extra_context):
    wrapped_sql = node.wrapped_sql
    context = _node_context(adapter, node)
    context.update(extra_context)
    sql = dbt.clients.jinja.get_rendered(wrapped_sql, context)
    node.wrapped_sql = sql
    return node

def _node_context(adapter, node):
    return {
        "run_started_at": dbt.tracking.active_user.run_started_at,
        "invocation_id": dbt.tracking.active_user.invocation_id,
    }
<commit_msg>(fixes #1313) Show sources in resource count list during compilation<commit_after>import itertools
import os
import json
from collections import OrderedDict, defaultdict
import sqlparse

import dbt.utils
import dbt.include
import dbt.tracking

from dbt import deprecations
from dbt.utils import get_materialization, NodeType, is_type
from dbt.linker import Linker

import dbt.compat
import dbt.context.runtime
import dbt.contracts.project
import dbt.exceptions
import dbt.flags
import dbt.loader
import dbt.config
from dbt.contracts.graph.compiled import CompiledNode, CompiledGraph

from dbt.clients.system import write_json
from dbt.logger import GLOBAL_LOGGER as logger

graph_file_name = 'graph.gpickle'

def print_compile_stats(stats):
    names = {
        NodeType.Model: 'models',
        NodeType.Test: 'tests',
        NodeType.Archive: 'archives',
        NodeType.Analysis: 'analyses',
        NodeType.Macro: 'macros',
        NodeType.Operation: 'operations',
        NodeType.Seed: 'seed files',
        NodeType.Source: 'sources',
    }

    results = {k: 0 for k in names.keys()}
    results.update(stats)

    stat_line = ", ".join(
        ["{} {}".format(ct, names.get(t)) for t, ct in results.items()])

    logger.info("Found {}".format(stat_line))

def _add_prepended_cte(prepended_ctes, new_cte):
    for dct in prepended_ctes:
        if dct['id'] == new_cte['id']:
            dct['sql'] = new_cte['sql']
            return
    prepended_ctes.append(new_cte)

def _extend_prepended_ctes(prepended_ctes, new_prepended_ctes):
    for new_cte in new_prepended_ctes:
        _add_prepended_cte(prepended_ctes, new_cte)

def prepend_ctes(model, manifest):
    model, _, manifest = recursively_prepend_ctes(model, manifest)

    return (model, manifest)

def recursively_prepend_ctes(model, manifest):
    if model.extra_ctes_injected:
        return (model, model.extra_ctes, manifest)

    if dbt.flags.STRICT_MODE:
        # ensure that the cte we're adding to is compiled
        CompiledNode(**model.serialize())

    prepended_ctes = []

    for cte in model.extra_ctes:
        cte_id = cte['id']
        cte_to_add = manifest.nodes.get(cte_id)
        cte_to_add, new_prepended_ctes, manifest = recursively_prepend_ctes(
            cte_to_add, manifest)
        _extend_prepended_ctes(prepended_ctes, new_prepended_ctes)
        new_cte_name = '__dbt__CTE__{}'.format(cte_to_add.get('name'))
        sql = ' {} as (\n{}\n)'.format(new_cte_name, cte_to_add.compiled_sql)
        _add_prepended_cte(prepended_ctes, {'id': cte_id, 'sql': sql})

    model.prepend_ctes(prepended_ctes)

    manifest.nodes[model.unique_id] = model

    return (model, prepended_ctes, manifest)

class Compiler(object):
    def __init__(self, config):
        self.config = config

    def initialize(self):
        dbt.clients.system.make_directory(self.config.target_path)
        dbt.clients.system.make_directory(self.config.modules_path)

    def compile_node(self, node, manifest, extra_context=None):
        if extra_context is None:
            extra_context = {}

        logger.debug("Compiling {}".format(node.get('unique_id')))

        data = node.to_dict()
        data.update({
            'compiled': False,
            'compiled_sql': None,
            'extra_ctes_injected': False,
            'extra_ctes': [],
            'injected_sql': None,
        })
        compiled_node = CompiledNode(**data)

        context = dbt.context.runtime.generate(
            compiled_node, self.config, manifest)
        context.update(extra_context)

        compiled_node.compiled_sql = dbt.clients.jinja.get_rendered(
            node.get('raw_sql'),
            context,
            node)

        compiled_node.compiled = True

        injected_node, _ = prepend_ctes(compiled_node, manifest)

        should_wrap = {NodeType.Test, NodeType.Operation}
        if injected_node.resource_type in should_wrap:
            # data tests get wrapped in count(*)
            # TODO : move this somewhere more reasonable
            if 'data' in injected_node.tags and \
               is_type(injected_node, NodeType.Test):
                injected_node.wrapped_sql = (
                    "select count(*) from (\n{test_sql}\n) sbq").format(
                        test_sql=injected_node.injected_sql)
            else:
                # don't wrap schema tests or analyses.
                injected_node.wrapped_sql = injected_node.injected_sql

        elif is_type(injected_node, NodeType.Archive):
            # unfortunately we do everything automagically for
            # archives. in the future it'd be nice to generate
            # the SQL at the parser level.
            pass

        elif(is_type(injected_node, NodeType.Model) and
             get_materialization(injected_node) == 'ephemeral'):
            pass

        else:
            injected_node.wrapped_sql = None

        return injected_node

    def write_graph_file(self, linker, manifest):
        filename = graph_file_name
        graph_path = os.path.join(self.config.target_path, filename)
        linker.write_graph(graph_path, manifest)

    def link_node(self, linker, node, manifest):
        linker.add_node(node.unique_id)

        for dependency in node.depends_on_nodes:
            if manifest.nodes.get(dependency):
                linker.dependency(
                    node.unique_id,
                    (manifest.nodes.get(dependency).unique_id))
            else:
                dbt.exceptions.dependency_not_found(node, dependency)

    def link_graph(self, linker, manifest):
        for node in manifest.nodes.values():
            self.link_node(linker, node, manifest)

        cycle = linker.find_cycles()

        if cycle:
            raise RuntimeError("Found a cycle: {}".format(cycle))

    def compile(self, manifest):
        linker = Linker()

        self.link_graph(linker, manifest)

        stats = defaultdict(int)

        for node_name, node in itertools.chain(
                manifest.nodes.items(),
                manifest.macros.items()):
            stats[node.resource_type] += 1

        self.write_graph_file(linker, manifest)
        print_compile_stats(stats)

        return linker

def compile_manifest(config, manifest):
    compiler = Compiler(config)
    compiler.initialize()
    return compiler.compile(manifest)

def compile_node(adapter, config, node, manifest, extra_context):
    compiler = Compiler(config)
    node = compiler.compile_node(node, manifest, extra_context)
    node = _inject_runtime_config(adapter, node, extra_context)

    if(node.injected_sql is not None and
       not (dbt.utils.is_type(node, NodeType.Archive))):
        logger.debug('Writing injected SQL for node "{}"'.format(
            node.unique_id))

        written_path = dbt.writer.write_node(
            node,
            config.target_path,
            'compiled',
            node.injected_sql)

        node.build_path = written_path

    return node

def _inject_runtime_config(adapter, node, extra_context):
    wrapped_sql = node.wrapped_sql
    context = _node_context(adapter, node)
    context.update(extra_context)
    sql = dbt.clients.jinja.get_rendered(wrapped_sql, context)
    node.wrapped_sql = sql
    return node

def _node_context(adapter, node):
    return {
        "run_started_at": dbt.tracking.active_user.run_started_at,
        "invocation_id": dbt.tracking.active_user.invocation_id,
    }
<|endoftext|>


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

import os
import sys

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

version = "1.0.1"

if sys.argv[-1] == 'publish':
    try:
        import wheel  # noqa
    except ImportError:
        raise ImportError("Fix: pip install wheel")
    os.system('python setup.py sdist bdist_wheel upload')
    print("You probably want to also tag the version now:")
    print("  git tag -a %s -m 'version %s'" % (version, version))
    print("  git push --tags")
    sys.exit()

if sys.argv[-1] == 'tag':
    print("Tagging the version on github:")
    os.system("git tag -a %s -m 'version %s'" % (version, version))
    os.system("git push --tags")
    sys.exit()

readme = open('README.rst').read()
history = open('HISTORY.rst').read().replace('.. :changelog:', '')

def get_requirements(filename):
    with open(filename) as fp:
        reqs = [x.strip() for x in fp.read().splitlines()
                if not x.strip().startswith('#')]
    return reqs

setup(
    name='stupid',
    version=version,
    description="""Slack super perfomance team collaboration bot""",
    long_description=readme + '\n\n' + history,
    author='Petr Demin',
    author_email='petr.demin@nih.gov',
    url='https://github.com/peterdemin/stupid',
    include_package_data=True,
    py_modules=['stupid'],
    install_requires=get_requirements('requirements.txt'),
    license="BSD",
    zip_safe=False,
    keywords='stupid',
    classifiers=[
        'Development Status :: 2 - Pre-Alpha',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Natural Language :: English',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
    ],
    entry_points={
        'console_scripts': [
            'stupid = stupid:main',
        ]
    },
    setup_requires=['nose>=1.0'],
    tests_require=get_requirements('requirements-dev.txt'),
)
<commit_msg>Fixed required Python version<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

version = "1.0.1"

if sys.argv[-1] == 'publish':
    try:
        import wheel  # noqa
    except ImportError:
        raise ImportError("Fix: pip install wheel")
    os.system('python setup.py sdist bdist_wheel upload')
    print("You probably want to also tag the version now:")
    print("  git tag -a %s -m 'version %s'" % (version, version))
    print("  git push --tags")
    sys.exit()

if sys.argv[-1] == 'tag':
    print("Tagging the version on github:")
    os.system("git tag -a %s -m 'version %s'" % (version, version))
    os.system("git push --tags")
    sys.exit()

readme = open('README.rst').read()
history = open('HISTORY.rst').read().replace('.. :changelog:', '')

def get_requirements(filename):
    with open(filename) as fp:
        reqs = [x.strip() for x in fp.read().splitlines()
                if not x.strip().startswith('#')]
    return reqs

setup(
    name='stupid',
    version=version,
    description="""Slack super perfomance team collaboration bot""",
    long_description=readme + '\n\n' + history,
    author='Petr Demin',
    author_email='petr.demin@nih.gov',
    url='https://github.com/peterdemin/stupid',
    include_package_data=True,
    py_modules=['stupid'],
    install_requires=get_requirements('requirements.txt'),
    license="BSD",
    zip_safe=False,
    keywords='stupid',
    classifiers=[
        'Development Status :: 2 - Pre-Alpha',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Natural Language :: English',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
    ],
    entry_points={
        'console_scripts': [
            'stupid = stupid:main',
        ]
    },
    setup_requires=['nose>=1.0'],
    tests_require=get_requirements('requirements-dev.txt'),
)
<|endoftext|>