<commit_before># -*- coding: utf-8 -*-

"""
Functions and classes for adapter working

@author: Seva Zhidkov
@contact: zhidkovseva@gmail.com
@license: The MIT license

Copyright (C) 2015
"""

from importlib import import_module
from time import sleep

from sheldon.utils import logger

class Adapter:
    """
    Adapter class contains information about adapter:
    name, variables and module using to call adapter methods.
    All adapters inherited from Adapter class'
    """

    def __init__(self, name, module):
        """
        Init new Adapter object

        :param name: public name of adapter which used in
                     config/adapters directory
        :param variables: variables of adapters which set
                          in os.environ or in config.py.
                          Example of adapter variable - Slack API key.
        :param module: imported adapter's module
        """
        self.name = name
        self.module = module

class Message:
    """
    Class for every message: incoming and outcoming.
    """

    def __init__(self, message_text, message_attachments, channel=None, variables={}):
        """
        Create new message.

        :param text: string, text of message
        :param message_attachments: list[Attachment] or Attachment object,
                                    attachments with message
        :param channel: Message's channel: channel in Slack, room in Hipchat etc.
        :param variables: dict, external parameters from/to adapter:
                          may be 'slack_username', 'slack_emoji', 'telegram_id'
                          Read about those in adapters' documentation.
                          Parameters should start from adapter name.
        """
        self.text = message_text
        # If attachment only one, convert it to list
        if type(message_attachments) == Attachment:
            self.attachments = [message_attachments]
        else:
            self.attachments = message_attachments
        self.channel = channel
        self.variables = variables

class IncomingMessage(Message):
    """
    Class for messages from user.
    """

    def __init__(self, sender, *args, **kwargs):
        """
        Create new message from user.

        :param sender: User object, sender of message
        """
        super().__init__(*args, **kwargs)
        self.sender = sender

class OutgoingMessage(Message):
    """
    Class for messages from bot.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class Attachment:
    """
    Class for every attachment: incoming and outgoing
    """

    def __init__(self, attachment_type, attachment_path=None,
                 attachment_text='', attachment_id=None):
        """
        Init new attachment.

        :param attachment_type: 'photo', 'video', 'link'
                                and other things, supported by adapter
        :param attachment_path: path to photo, video, url etc.
        :param attachment_text: optional text of attachment - may
                                be сaption or something another
        :param attachment_id: int, not required id of attachment from adapter
        :return:
        """
        self.type = attachment_type.lower()
        self.path = attachment_path
        self.text = attachment_text
        self.id = attachment_id

def load_adapter(bot):
    """
    Load adapter which set in bot config.

    :param bot: Bot object
    :return:
    """
    adapter_name = bot.config.get('SHELDON_ADAPTER_NAME', required=True)
    try:
        adapter_module = import_module('adapters.{}'.format(
            bot.config.get('SHELDON_ADAPTER_NAME')
        ))
    except ImportError as error:
        logger.critical_message("Error while loading plugin: \n" +
                                    str(error.__traceback__))
        return None
    adapter_module.on_start(bot)
    adapter_object = Adapter(adapter_name,
                             adapter_module)
    return adapter_object
<commit_msg>Delete docstring for unusable argument in init adapter function<commit_after># -*- coding: utf-8 -*-

"""
Functions and classes for adapter working

@author: Seva Zhidkov
@contact: zhidkovseva@gmail.com
@license: The MIT license

Copyright (C) 2015
"""

from importlib import import_module
from time import sleep

from sheldon.utils import logger

class Adapter:
    """
    Adapter class contains information about adapter:
    name, variables and module using to call adapter methods.
    All adapters inherited from Adapter class'
    """

    def __init__(self, name, module):
        """
        Init new Adapter object

        :param name: public name of adapter which used in
                     config/adapters directory
        :param module: imported adapter's module
        """
        self.name = name
        self.module = module

class Message:
    """
    Class for every message: incoming and outcoming.
    """

    def __init__(self, message_text, message_attachments, channel=None, variables={}):
        """
        Create new message.

        :param text: string, text of message
        :param message_attachments: list[Attachment] or Attachment object,
                                    attachments with message
        :param channel: Message's channel: channel in Slack, room in Hipchat etc.
        :param variables: dict, external parameters from/to adapter:
                          may be 'slack_username', 'slack_emoji', 'telegram_id'
                          Read about those in adapters' documentation.
                          Parameters should start from adapter name.
        """
        self.text = message_text
        # If attachment only one, convert it to list
        if type(message_attachments) == Attachment:
            self.attachments = [message_attachments]
        else:
            self.attachments = message_attachments
        self.channel = channel
        self.variables = variables

class IncomingMessage(Message):
    """
    Class for messages from user.
    """

    def __init__(self, sender, *args, **kwargs):
        """
        Create new message from user.

        :param sender: User object, sender of message
        """
        super().__init__(*args, **kwargs)
        self.sender = sender

class OutgoingMessage(Message):
    """
    Class for messages from bot.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class Attachment:
    """
    Class for every attachment: incoming and outgoing
    """

    def __init__(self, attachment_type, attachment_path=None,
                 attachment_text='', attachment_id=None):
        """
        Init new attachment.

        :param attachment_type: 'photo', 'video', 'link'
                                and other things, supported by adapter
        :param attachment_path: path to photo, video, url etc.
        :param attachment_text: optional text of attachment - may
                                be сaption or something another
        :param attachment_id: int, not required id of attachment from adapter
        :return:
        """
        self.type = attachment_type.lower()
        self.path = attachment_path
        self.text = attachment_text
        self.id = attachment_id

def load_adapter(bot):
    """
    Load adapter which set in bot config.

    :param bot: Bot object
    :return:
    """
    adapter_name = bot.config.get('SHELDON_ADAPTER_NAME', required=True)
    try:
        adapter_module = import_module('adapters.{}'.format(
            bot.config.get('SHELDON_ADAPTER_NAME')
        ))
    except ImportError as error:
        logger.critical_message("Error while loading plugin: \n" +
                                    str(error.__traceback__))
        return None
    adapter_module.on_start(bot)
    adapter_object = Adapter(adapter_name,
                             adapter_module)
    return adapter_object
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
## $Id$
##
## This file is part of CDS Invenio.
## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN.
##
## CDS Invenio is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of the
## License, or (at your option) any later version.
##
## CDS Invenio is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with CDS Invenio; if not, write to the Free Software Foundation, Inc.,
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""
CDS Invenio international messages functions, to be used by all
I18N interfaces.  Typical usage in the caller code is:

   from messages import gettext_set_language
   [...]
   def square(x, ln=CFG_SITE_LANG):
       _ = gettext_set_language(ln)
       print _("Hello there!")
       print _("The square of %s is %s.") % (x, x*x)

In the caller code, all output strings should be made translatable via
the _() convention.

For more information, see ABOUT-NLS file.
"""

__revision__ = "$Id$"

import gettext

from invenio.config import CFG_LOCALEDIR, CFG_SITE_LANGS

_LANG_GT_D = {}
for _alang in CFG_SITE_LANGS:
    _LANG_GT_D[_alang] = gettext.translation('cds-invenio',
                                             CFG_LOCALEDIR,
                                             languages = [_alang],
                                             fallback = True)

def gettext_set_language(ln):
    """
      Set the _ gettext function in every caller function

      Usage::
        _ = gettext_set_language(ln)
    """
    return _LANG_GT_D[ln].gettext

def wash_language(ln):
    """Look at language LN and check if it is one of allowed languages
       for the interface.  Return it in case of success, return the
       default language otherwise."""
    if ln in CFG_SITE_LANGS:
        return ln
    else:
        return 'en'

def wash_languages(lns):
    """Look at list of languages LNS and check if there's at least one
       of the allowed languages for the interface. Return it in case
       of success, return the default language otherwise."""
    for ln in lns:
        if ln in CFG_SITE_LANGS:
            return ln
        else:
            return 'en'

def language_list_long():
    """Return list of [short name, long name] for all enabled languages."""
    all_language_names = {'bg': 'Български',
                          'ca': 'Català',
                          'cs': 'Česky',
                          'de': 'Deutsch',
                          'el': 'Ελληνικά',
                          'en': 'English',
                          'es': 'Español',
                          'fr': 'Français',
                          'hr': 'Hrvatski',
                          'it': 'Italiano',
                          'ja': '日本語',
                          'no': 'Norsk/Bokmål',
                          'pl': 'Polski',
                          'pt': 'Português',
                          'ru': 'Русский',
                          'sk': 'Slovensky',
                          'sv': 'Svenska',
                          'uk': 'Українська',
                          'zh_CN': '中文(简)',
                          'zh_TW': '中文(繁)',}
    enabled_lang_list = []
    for lang in CFG_SITE_LANGS:
        enabled_lang_list.append([lang, all_language_names[lang]])
    return enabled_lang_list
<commit_msg>Do not force English as default, take CFG_SITE_LANG.<commit_after># -*- coding: utf-8 -*-
## $Id$
##
## This file is part of CDS Invenio.
## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN.
##
## CDS Invenio is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of the
## License, or (at your option) any later version.
##
## CDS Invenio is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with CDS Invenio; if not, write to the Free Software Foundation, Inc.,
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

"""
CDS Invenio international messages functions, to be used by all
I18N interfaces.  Typical usage in the caller code is:

   from messages import gettext_set_language
   [...]
   def square(x, ln=CFG_SITE_LANG):
       _ = gettext_set_language(ln)
       print _("Hello there!")
       print _("The square of %s is %s.") % (x, x*x)

In the caller code, all output strings should be made translatable via
the _() convention.

For more information, see ABOUT-NLS file.
"""

__revision__ = "$Id$"

import gettext

from invenio.config import CFG_LOCALEDIR, CFG_SITE_LANG, CFG_SITE_LANGS

_LANG_GT_D = {}
for _alang in CFG_SITE_LANGS:
    _LANG_GT_D[_alang] = gettext.translation('cds-invenio',
                                             CFG_LOCALEDIR,
                                             languages = [_alang],
                                             fallback = True)

def gettext_set_language(ln):
    """
      Set the _ gettext function in every caller function

      Usage::
        _ = gettext_set_language(ln)
    """
    return _LANG_GT_D[ln].gettext

def wash_language(ln):
    """Look at language LN and check if it is one of allowed languages
       for the interface.  Return it in case of success, return the
       default language otherwise."""
    if ln in CFG_SITE_LANGS:
        return ln
    else:
        return CFG_SITE_LANG

def wash_languages(lns):
    """Look at list of languages LNS and check if there's at least one
       of the allowed languages for the interface. Return it in case
       of success, return the default language otherwise."""
    for ln in lns:
        if ln in CFG_SITE_LANGS:
            return ln
        else:
            return CFG_SITE_LANG

def language_list_long():
    """Return list of [short name, long name] for all enabled languages."""
    all_language_names = {'bg': 'Български',
                          'ca': 'Català',
                          'cs': 'Česky',
                          'de': 'Deutsch',
                          'el': 'Ελληνικά',
                          'en': 'English',
                          'es': 'Español',
                          'fr': 'Français',
                          'hr': 'Hrvatski',
                          'it': 'Italiano',
                          'ja': '日本語',
                          'no': 'Norsk/Bokmål',
                          'pl': 'Polski',
                          'pt': 'Português',
                          'ru': 'Русский',
                          'sk': 'Slovensky',
                          'sv': 'Svenska',
                          'uk': 'Українська',
                          'zh_CN': '中文(简)',
                          'zh_TW': '中文(繁)',}
    enabled_lang_list = []
    for lang in CFG_SITE_LANGS:
        enabled_lang_list.append([lang, all_language_names[lang]])
    return enabled_lang_list
<|endoftext|>


<commit_before>import urllib
import urllib2
import json
from collections import defaultdict
import time
import random

GCM_URL = 'https://android.googleapis.com/gcm/send'

class GCMException(Exception):
    pass

class GCMMalformedJsonException(GCMException):
    pass

class GCMConnectionException(GCMException):
    pass

class GCMAuthenticationException(GCMException):
    pass

class GCMTooManyRegIdsException(GCMException):
    pass

class GCMInvalidTtlException(GCMException):
    pass

# Exceptions from Google responses

class GCMMissingRegistrationException(GCMException):
    pass

class GCMMismatchSenderIdException(GCMException):
    pass

class GCMNotRegisteredException(GCMException):
    pass

class GCMMessageTooBigException(GCMException):
    pass

class GCMInvalidRegistrationException(GCMException):
    pass

class GCMUnavailableException(GCMException):
    pass

# TODO: Refactor this to be more human-readable
def group_response(response, registration_ids, key):
    # Pair up results and reg_ids
    mapping = zip(registration_ids, response['results'])
    # Filter by key
    filtered = ((reg_id, res[key]) for reg_id, res in mapping if key in res)
    # Grouping of errors and mapping of ids
    if key is 'registration_id':
        grouping = dict(filtered)
    else:
        grouping = defaultdict(list)
        for k, v in filtered:
            grouping[v].append(k)

    return grouping or None

def urlencode_utf8(params):
    """
    UTF-8 safe variant of urllib.urlencode.
    http://stackoverflow.com/a/8152242
    """

    if hasattr(params, 'items'):
        params = params.items()

    params = (
        '='.join((
            urllib.quote_plus(k.encode('utf8'), safe='/'),
            urllib.quote_plus(v.encode('utf8'), safe='/')
        )) for k, v in params
    )

    return '&'.join(params)

class GCM(object):

    # Timeunit is milliseconds.
    BACKOFF_INITIAL_DELAY = 1000
    MAX_BACKOFF_DELAY = 1024000

    def __init__(self, api_key, url=GCM_URL, proxy=None):
        """ api_key : google api key
            url: url of gcm service.
            proxy: can be string "http://host:port" or dict {'https':'host:port'}
        """
        self.api_key = api_key
        self.url = url
        if proxy:
            if isinstance(proxy, basestring):
                protocol = url.split(':')[0]
                proxy = {protocol: proxy}

            auth = urllib2.HTTPBasicAuthHandler()
            opener = urllib2.build_opener(
                urllib2.ProxyHandler(proxy), auth, urllib2.HTTPHandler)
            urllib2.install_opener(opener)

    def construct_payload(self, registration_ids, data=None, collapse_key=None,
                          delay_while_idle=False, time_to_live=None, is_json=True, dry_run=False):
        """
        Construct the dictionary mapping of parameters.
        Encodes the dictionary into JSON if for json requests.
        Helps appending 'data.' prefix to the plaintext data: 'hello' => 'data.hello'

        :return constructed dict or JSON payload
        :raises GCMInvalidTtlException: if time_to_live is invalid
        """

        if time_to_live:
            if time_to_live > 2419200 or time_to_live < 0:
                raise GCMInvalidTtlException("Invalid time to live value")

        if is_json:
            payload = {'registration_ids': registration_ids}
            if data:
                payload['data'] = data
        else:
            payload = {'registration_id': registration_ids}
            if data:
                plaintext_data = data.copy()
                for k in plaintext_data.keys():
                    plaintext_data['data.%s' % k] = plaintext_data.pop(k)
                payload.update(plaintext_data)

        if delay_while_idle:
            payload['delay_while_idle'] = delay_while_idle

        if time_to_live >= 0:
            payload['time_to_live'] = time_to_live

        if collapse_key:
            payload['collapse_key'] = collapse_key

        if dry_run:
            payload['dry_run'] = True

        if is_json:
            payload = json.dumps(payload)

        return payload

    def make_request(self, data, is_json=True):
        """
        Makes a HTTP request to GCM servers with the constructed payload

        :param data: return value from construct_payload method
        :raises GCMMalformedJsonException: if malformed JSON request found
        :raises GCMAuthenticationException: if there was a problem with authentication, invalid api key
        :raises GCMConnectionException: if GCM is screwed
        """

        headers = {
            'Authorization': 'key=%s' % self.api_key,
        }
        # Default Content-Type is defaulted to
        # application/x-www-form-urlencoded;charset=UTF-8
        if is_json:
            headers['Content-Type'] = 'application/json'

        if not is_json:
            data = urlencode_utf8(data)
        req = urllib2.Request(self.url, data, headers)

        try:
            response = urllib2.urlopen(req).read()
        except urllib2.HTTPError as e:
            if e.code == 400:
                raise GCMMalformedJsonException(
                    "The request could not be parsed as JSON")
            elif e.code == 401:
                raise GCMAuthenticationException(
                    "There was an error authenticating the sender account")
            elif e.code == 503:
                raise GCMUnavailableException("GCM service is unavailable")
            else:
                error = "GCM service error: %d" % e.code
                raise GCMUnavailableException(error)
        except urllib2.URLError as e:
            raise GCMConnectionException(
                "There was an internal error in the GCM server while trying to process the request")

        if is_json:
            response = json.loads(response)
        return response

    def raise_error(self, error):
        if error == 'InvalidRegistration':
            raise GCMInvalidRegistrationException("Registration ID is invalid")
        elif error == 'Unavailable':
            # Plain-text requests will never return Unavailable as the error code.
            # http://developer.android.com/guide/google/gcm/gcm.html#error_codes
            raise GCMUnavailableException(
                "Server unavailable. Resent the message")
        elif error == 'NotRegistered':
            raise GCMNotRegisteredException(
                "Registration id is not valid anymore")
        elif error == 'MismatchSenderId':
            raise GCMMismatchSenderIdException(
                "A Registration ID is tied to a certain group of senders")
        elif error == 'MessageTooBig':
            raise GCMMessageTooBigException("Message can't exceed 4096 bytes")

    def handle_plaintext_response(self, response):

        # Split response by line
        response_lines = response.strip().split('\n')
        # Split the first line by =
        key, value = response_lines[0].split('=')
        if key == 'Error':
            self.raise_error(value)
        else:
            if len(response_lines) == 2:
                return response_lines[1].split('=')[1]
            return

    def handle_json_response(self, response, registration_ids):
        errors = group_response(response, registration_ids, 'error')
        canonical = group_response(
            response, registration_ids, 'registration_id')

        info = {}
        if errors:
            info.update({'errors': errors})
        if canonical:
            info.update({'canonical': canonical})

        return info

    def extract_unsent_reg_ids(self, info):
        if 'errors' in info and 'Unavailable' in info['errors']:
            return info['errors']['Unavailable']
        return []

    def plaintext_request(self, registration_id, data=None, collapse_key=None,
                          delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
        """
        Makes a plaintext request to GCM servers

        :param registration_id: string of the registration id
        :param data: dict mapping of key-value pairs of messages
        :return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc
        :raises GCMMissingRegistrationException: if registration_id is not provided
        """

        if not registration_id:
            raise GCMMissingRegistrationException("Missing registration_id")

        payload = self.construct_payload(
            registration_id, data, collapse_key,
            delay_while_idle, time_to_live, False, dry_run
        )

        attempt = 0
        backoff = self.BACKOFF_INITIAL_DELAY
        for attempt in range(retries):
            try:
                response = self.make_request(payload, is_json=False)
                return self.handle_plaintext_response(response)
            except GCMUnavailableException:
                sleep_time = backoff / 2 + random.randrange(backoff)
                time.sleep(float(sleep_time) / 1000)
                if 2 * backoff < self.MAX_BACKOFF_DELAY:
                    backoff *= 2

        raise IOError("Could not make request after %d attempts" % attempt)

    def json_request(self, registration_ids, data=None, collapse_key=None,
                     delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
        """
        Makes a JSON request to GCM servers

        :param registration_ids: list of the registration ids
        :param data: dict mapping of key-value pairs of messages
        :return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc
        :raises GCMMissingRegistrationException: if the list of registration_ids is empty
        :raises GCMTooManyRegIdsException: if the list of registration_ids exceeds 1000 items
        """

        if not registration_ids:
            raise GCMMissingRegistrationException("Missing registration_ids")
        if len(registration_ids) > 1000:
            raise GCMTooManyRegIdsException(
                "Exceded number of registration_ids")

        attempt = 0
        backoff = self.BACKOFF_INITIAL_DELAY
        for attempt in range(retries):
            payload = self.construct_payload(
                registration_ids, data, collapse_key,
                delay_while_idle, time_to_live, True, dry_run
            )
            response = self.make_request(payload, is_json=True)
            info = self.handle_json_response(response, registration_ids)

            unsent_reg_ids = self.extract_unsent_reg_ids(info)
            if unsent_reg_ids:
                registration_ids = unsent_reg_ids
                sleep_time = backoff / 2 + random.randrange(backoff)
                time.sleep(float(sleep_time) / 1000)
                if 2 * backoff < self.MAX_BACKOFF_DELAY:
                    backoff *= 2
            else:
                break

        return info
<commit_msg>random cleanup<commit_after>import urllib
import urllib2
import json
from collections import defaultdict
import time
import random

GCM_URL = 'https://android.googleapis.com/gcm/send'

class GCMException(Exception):
    pass

class GCMMalformedJsonException(GCMException):
    pass

class GCMConnectionException(GCMException):
    pass

class GCMAuthenticationException(GCMException):
    pass

class GCMTooManyRegIdsException(GCMException):
    pass

class GCMInvalidTtlException(GCMException):
    pass

# Exceptions from Google responses

class GCMMissingRegistrationException(GCMException):
    pass

class GCMMismatchSenderIdException(GCMException):
    pass

class GCMNotRegisteredException(GCMException):
    pass

class GCMMessageTooBigException(GCMException):
    pass

class GCMInvalidRegistrationException(GCMException):
    pass

class GCMUnavailableException(GCMException):
    pass

# TODO: Refactor this to be more human-readable
def group_response(response, registration_ids, key):
    # Pair up results and reg_ids
    mapping = zip(registration_ids, response['results'])
    # Filter by key
    filtered = ((reg_id, res[key]) for reg_id, res in mapping if key in res)
    # Grouping of errors and mapping of ids
    if key is 'registration_id':
        grouping = dict(filtered)
    else:
        grouping = defaultdict(list)
        for k, v in filtered:
            grouping[v].append(k)

    return grouping or None

def urlencode_utf8(params):
    """
    UTF-8 safe variant of urllib.urlencode.
    http://stackoverflow.com/a/8152242
    """

    if hasattr(params, 'items'):
        params = params.items()

    params = (
        '='.join((
            urllib.quote_plus(k.encode('utf8'), safe='/'),
            urllib.quote_plus(v.encode('utf8'), safe='/')
        )) for k, v in params
    )

    return '&'.join(params)

class GCM(object):

    # Timeunit is milliseconds.
    BACKOFF_INITIAL_DELAY = 1000
    MAX_BACKOFF_DELAY = 1024000

    def __init__(self, api_key, url=GCM_URL, proxy=None):
        """ api_key : google api key
            url: url of gcm service.
            proxy: can be string "http://host:port" or dict {'https':'host:port'}
        """
        self.api_key = api_key
        self.url = url
        if proxy:
            if isinstance(proxy, basestring):
                protocol = url.split(':')[0]
                proxy = {protocol: proxy}

            auth = urllib2.HTTPBasicAuthHandler()
            opener = urllib2.build_opener(
                urllib2.ProxyHandler(proxy), auth, urllib2.HTTPHandler)
            urllib2.install_opener(opener)

    def construct_payload(self, registration_ids, data=None, collapse_key=None,
                          delay_while_idle=False, time_to_live=None, is_json=True, dry_run=False):
        """
        Construct the dictionary mapping of parameters.
        Encodes the dictionary into JSON if for json requests.
        Helps appending 'data.' prefix to the plaintext data: 'hello' => 'data.hello'

        :return constructed dict or JSON payload
        :raises GCMInvalidTtlException: if time_to_live is invalid
        """

        if time_to_live:
            four_weeks_in_secs = 2419200
            if not (0 <= time_to_live <= four_weeks_in_secs):
                raise GCMInvalidTtlException("Invalid time to live value")

        if is_json:
            payload = {'registration_ids': registration_ids}
            if data:
                payload['data'] = data
        else:
            payload = {'registration_id': registration_ids}
            if data:
                plaintext_data = data.copy()
                for k in plaintext_data.keys():
                    plaintext_data['data.%s' % k] = plaintext_data.pop(k)
                payload.update(plaintext_data)

        if delay_while_idle:
            payload['delay_while_idle'] = delay_while_idle

        if time_to_live:
            payload['time_to_live'] = time_to_live

        if collapse_key:
            payload['collapse_key'] = collapse_key

        if dry_run:
            payload['dry_run'] = True

        if is_json:
            payload = json.dumps(payload)

        return payload

    def make_request(self, data, is_json=True):
        """
        Makes a HTTP request to GCM servers with the constructed payload

        :param data: return value from construct_payload method
        :raises GCMMalformedJsonException: if malformed JSON request found
        :raises GCMAuthenticationException: if there was a problem with authentication, invalid api key
        :raises GCMConnectionException: if GCM is screwed
        """

        headers = {
            'Authorization': 'key=%s' % self.api_key,
        }
        # Default Content-Type is defaulted to
        # application/x-www-form-urlencoded;charset=UTF-8
        if is_json:
            headers['Content-Type'] = 'application/json'

        if not is_json:
            data = urlencode_utf8(data)
        req = urllib2.Request(self.url, data, headers)

        try:
            response = urllib2.urlopen(req).read()
        except urllib2.HTTPError as e:
            if e.code == 400:
                raise GCMMalformedJsonException(
                    "The request could not be parsed as JSON")
            elif e.code == 401:
                raise GCMAuthenticationException(
                    "There was an error authenticating the sender account")
            elif e.code == 503:
                raise GCMUnavailableException("GCM service is unavailable")
            else:
                error = "GCM service error: %d" % e.code
                raise GCMUnavailableException(error)
        except urllib2.URLError as e:
            raise GCMConnectionException(
                "There was an internal error in the GCM server while trying to process the request")

        if is_json:
            response = json.loads(response)
        return response

    def raise_error(self, error):
        if error == 'InvalidRegistration':
            raise GCMInvalidRegistrationException("Registration ID is invalid")
        elif error == 'Unavailable':
            # Plain-text requests will never return Unavailable as the error code.
            # http://developer.android.com/guide/google/gcm/gcm.html#error_codes
            raise GCMUnavailableException(
                "Server unavailable. Resent the message")
        elif error == 'NotRegistered':
            raise GCMNotRegisteredException(
                "Registration id is not valid anymore")
        elif error == 'MismatchSenderId':
            raise GCMMismatchSenderIdException(
                "A Registration ID is tied to a certain group of senders")
        elif error == 'MessageTooBig':
            raise GCMMessageTooBigException("Message can't exceed 4096 bytes")

    def handle_plaintext_response(self, response):

        # Split response by line
        response_lines = response.strip().split('\n')
        # Split the first line by =
        key, value = response_lines[0].split('=')
        if key == 'Error':
            self.raise_error(value)
        else:
            if len(response_lines) == 2:
                return response_lines[1].split('=')[1]
            return

    def handle_json_response(self, response, registration_ids):
        errors = group_response(response, registration_ids, 'error')
        canonical = group_response(
            response, registration_ids, 'registration_id')

        info = {}
        if errors:
            info.update({'errors': errors})
        if canonical:
            info.update({'canonical': canonical})

        return info

    def extract_unsent_reg_ids(self, info):
        if 'errors' in info and 'Unavailable' in info['errors']:
            return info['errors']['Unavailable']
        return []

    def plaintext_request(self, registration_id, data=None, collapse_key=None,
                          delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
        """
        Makes a plaintext request to GCM servers

        :param registration_id: string of the registration id
        :param data: dict mapping of key-value pairs of messages
        :return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc
        :raises GCMMissingRegistrationException: if registration_id is not provided
        """

        if not registration_id:
            raise GCMMissingRegistrationException("Missing registration_id")

        payload = self.construct_payload(
            registration_id, data, collapse_key,
            delay_while_idle, time_to_live, False, dry_run
        )

        attempt = 0
        backoff = self.BACKOFF_INITIAL_DELAY
        for attempt in range(retries):
            try:
                response = self.make_request(payload, is_json=False)
                return self.handle_plaintext_response(response)
            except GCMUnavailableException:
                sleep_time = backoff / 2 + random.randrange(backoff)
                time.sleep(float(sleep_time) / 1000)
                if 2 * backoff < self.MAX_BACKOFF_DELAY:
                    backoff *= 2

        raise IOError("Could not make request after %d attempts" % attempt)

    def json_request(self, registration_ids, data=None, collapse_key=None,
                     delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
        """
        Makes a JSON request to GCM servers

        :param registration_ids: list of the registration ids
        :param data: dict mapping of key-value pairs of messages
        :return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc
        :raises GCMMissingRegistrationException: if the list of registration_ids is empty
        :raises GCMTooManyRegIdsException: if the list of registration_ids exceeds 1000 items
        """

        if not registration_ids:
            raise GCMMissingRegistrationException("Missing registration_ids")
        if len(registration_ids) > 1000:
            raise GCMTooManyRegIdsException(
                "Exceded number of registration_ids")

        attempt = 0
        backoff = self.BACKOFF_INITIAL_DELAY
        for attempt in range(retries):
            payload = self.construct_payload(
                registration_ids, data, collapse_key,
                delay_while_idle, time_to_live, True, dry_run
            )
            response = self.make_request(payload, is_json=True)
            info = self.handle_json_response(response, registration_ids)

            unsent_reg_ids = self.extract_unsent_reg_ids(info)
            if unsent_reg_ids:
                registration_ids = unsent_reg_ids
                sleep_time = backoff / 2 + random.randrange(backoff)
                time.sleep(float(sleep_time) / 1000)
                if 2 * backoff < self.MAX_BACKOFF_DELAY:
                    backoff *= 2
            else:
                break

        return info
<|endoftext|>


<commit_before>#!/usr/bin/python
import sys
from lxml import etree
import urllib
import gzip
import io
import time
import cmd

# TODO
# Some systems have more than one name
# Kepler-47 (circumbinary) has an error I think because there's no mass
# There's something weird with the system name autocomplete

try:
    from enum import Enum
except ImportError:
    print "You need the enum backport."
    print "pip install enum34"

try:
    from colorama import Fore, Back, Style, init
    init()
except ImportError:
    # colorama support is optional.
    pass

class PlanetSize(Enum):
    """Defines fuzzy planet size labels.

    The enum value is an ASCII depiction."""
    terrestrial = '.'
    neptune = 'o'
    jupiter = '@'
    unknown = '?'

#Constants
JUPITER_IN_EARTH_MASSES = 317.828
NEPTUNE_IN_EARTH_MASSES = 17.147
JUPITER_IN_EARTH_RADII = 11.209 #equatorial
NEPTUNE_IN_EARTH_RADII = 3.883 #equatorial

SYMBOL = {'sun': u'\u2609',
          'earth': u'\u2295',
          'neptune': u'\u2646',
          'jupiter': u'\u2643'}

if not sys.modules.has_key('colorama'):
    # Doesn't wreck things if colorama isn't installed.
    STARCOLOR = {'O': '',
                 'B': '',
                 'A': '',
                 'F': '',
                 'G': '',
                 'K': '',
                 'M': ''}
    RSTCOLOR = ''
else:
    STARCOLOR = {'O': Fore.MAGENTA + Style.BRIGHT,
                 'B': Fore.BLUE + Style.BRIGHT,
                 'A': Fore.WHITE + Style.BRIGHT,
                 'F': Fore.YELLOW + Style.BRIGHT,
                 'G': Fore.YELLOW + Style.BRIGHT,
                 'K': Fore.YELLOW,
                 'M': Fore.RED + Style.BRIGHT}
    RSTCOLOR = Fore.RESET + Style.RESET_ALL

def demo():
    # Output mass and radius of all planets
    for planet in oec.findall(".//planet"):
        print [planet.findtext("mass"), planet.findtext("radius")]

    # Find all circumbinary planets
    for planet in oec.findall(".//binary/planet"):
        print planet.findtext("name")

    # Output distance to planetary system (in pc, if known) and number of planets in system
    for system in oec.findall(".//system"):
        print system.findtext("distance"), len(system.findall(".//planet"))

def get_planet_radius(planet, multiple='jupiter'):
    """Returns planet radius as a float times Jupiter Radii

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    radius_node = planet.find('radius')
    if radius_node is None:
        return None
    jup_radii = float(radius_node.text)
    if multiple == 'jupiter':
        return jup_radii
    elif multiple == 'neptune':
        return (jup_radii * JUPITER_IN_EARTH_RADII) / NEPTUNE_IN_EARTH_RADII
    elif multiple == 'earth':
        return jup_radii * JUPITER_IN_EARTH_RADII

def get_planet_mass(planet, multiple='jupiter'):
    """Returns planet mass as a float times mass of multiple.

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    if planet.find('mass') is not None:
        jup_mass = float(planet.find('mass').text)
    else:
        return None
    if multiple == 'jupiter':
        return jup_mass
    elif multiple == 'neptune':
        return (jup_mass * JUPITER_IN_EARTH_MASSES) / NEPTUNE_IN_EARTH_MASSES
    elif multiple == 'earth':
        return jup_mass * JUPITER_IN_EARTH_MASSES

def get_planet_size(planet):
    """Returns PlanetSize enum for planet.

    Makes determination based on radius and mass criteria.
    :param planet: lxml etree node for <planet>
    """
    earth_radii = get_planet_radius(planet, 'earth')
    if earth_radii is not None:
        if earth_radii < 2:
            return PlanetSize.terrestrial
        elif earth_radii > 7:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    else:
        earth_masses = get_planet_mass(planet, 'earth')
        if earth_masses < 10:
            return PlanetSize.terrestrial
        elif earth_masses > 30:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    return PlanetSize.unknown

def get_etree():
    url = "https://github.com/OpenExoplanetCatalogue/oec_gzip/raw/master/systems.xml.gz"
    fo = io.BytesIO(urllib.urlopen(url).read())
    tree = etree.parse(gzip.GzipFile(fileobj=fo))
    return tree

def find_system_by_name(name):
    """Returns system node matching supplied name

    :param name: string containing desired system name.
    """
    system_name = name.replace('_',' ')
    target = tree.xpath('.//system/name[text()="'+system_name+'"]')
    return get_parent_tag(target[0], 'system')

def most_recent_planet(tree):
    """Returns planet node that has most recent update date.

    :param tree: lxml etree
    :returns: lxml etree
    """
    updates = tree.findall('.//lastupdate')
    update_time = [time.strptime(date.text,'%y/%m/%d') for date in updates]
    planet = updates[update_time.index(max(update_time))].getparent()
    return planet

def most_recent_system(tree):
    """Returns system with most recently updated planet.

    :param tree: lxml etree
    """
    planet = most_recent_planet(tree)
    return get_parent_tag(planet, 'system')

def get_parent_tag(node, tag):
    """Traverses up the tree from node until it finds tag and then returns that node.

    :param node: The child node to start the search from
    :param tag: String with tag type we're searching for.
    """
    if node.tag == tag:
        return node
    else:
        return get_parent_tag(node.getparent(), tag)

def num_planets(tree):
    """Returns the total number of planets in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//planet)"))

def num_stars(tree):
    """Returns the total number of stars in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//star)"))

def num_systems(tree):
    """Returns the total number of systems in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//system)"))

def largest_system(tree):
    """Returns system node that has the largest number of planets.

    :param tree: lxml etree
    :returns: lxml etree centered on system with the mode planets.
    """
    system_size = [num_planets(system) for system in tree.xpath(".//system")]
    largest_idx = system_size.index(max(system_size))
    root = tree.xpath("//systems")[0]
    return root[largest_idx]

def write_tree(tree,fn):
    xmlstr = etree.tostring(tree, pretty_print=True)
    with open(fn, 'w') as f:
        f.write(xmlstr)

def planet_name(planet):
    """Returns name of planet, just letter.
    """
    return planet.find('name').text.split(' ')[-1]

def spectral_colorize(s, star):
    """Returns string s with spectral color colorama codes
    """
    spectral = star.find('spectraltype').text
    stellarclass = spectral[0]
    if stellarclass in STARCOLOR.keys():
        return STARCOLOR[stellarclass] + s + RSTCOLOR
    else:
        return s

def spectral_name(star):
    """Retruns string containing spectral name of star, with colorama color

    Doesn't include ANSII code if colorama is not installed.
    :param star: lxml etree on <star> node
    """
    spectral = star.find('spectraltype').text
    return spectral_colorize(spectral, star)

def summarize_star(star):
    """return one line summary of star"""
    if star.find('name').text[-2] == ' ':
        name = star.find('name').text[-1]
    else:
        name = ' '
    return u'{0} {1} {2}{3}'.format(name, spectral_name(star), star.find('mass').text, 'M'+SYMBOL['sun'])

def format_planet_mass_str(planet):
    """Takes float multiple of Jupiter mass, and returns it rounded and prefixed.

    Uses J2.3 for jupiter masses.
    Uses E3.4 for earth masses if under 0.05 jupiter masses.
    """
    jup_mass = get_planet_mass(planet, 'jupiter')
    if jup_mass is None:
        return '  ?M{}'.format(SYMBOL['jupiter'])
    else:
        planet_size = get_planet_size(planet)
        flt_mass = float(planet.find('mass').text)
        if planet_size.name == 'terrestrial':
            use_mass = get_planet_mass(planet, 'earth')
            symbol = SYMBOL['earth']
        else:
            use_mass = jup_mass
            symbol = SYMBOL['jupiter']
        return u'{:0.3f}M{}'.format(round(use_mass, 3), symbol)

def summarize_planet(planet):
    """Return one line summary of planet"""
    if planet.find('list').text == "Confirmed planets":
        reliable = ' '
    else:
        reliable = '?'

    letter = planet_name(planet)

    mass = format_planet_mass_str(planet)
    return u'{0} {1} {2}'.format(reliable, letter, mass)

def summarize_system(system):
    """Prints concise summary of system represented by tree

    :param system: ommi clxml etree based on <system> tag.
    """
    s = []
    s.append(system.find('name').text + ' - ' + str(num_stars(system)) + ' stars - ' + str(num_planets(system)) + ' planets')
    s.append(ascii_system(system))
    s.append('')
    binary = system.find('binary')
    if binary is not None:
        system = binary
    for star in system.iterfind('star'):
        s.append(' ' + summarize_star(star))
        for planet in star.iterfind('planet'):
            s.append('   ' + summarize_planet(planet))
    if system.find('planet') is not None:
        s.append(' (..) circumbinary planets:')
    for planet in system.iterfind('planet'):
        s.append('   ' + summarize_planet(planet))
    return '\n'.join(s)

def get_max_sma(tree):
    """Returns max Semi-Major Axis (AU) for all planets in tree.

    :param tree: An lxml etree
    """
    allsmas = [float(x.text) for x in tree.findall('.//semimajoraxis')]
    return max(allsmas)

def ascii_system(system):
    """Return an ASCII graphic of the system.

    :param system: lxml etree based on <system> tag.
    """
    dots = []
    names = []
    maxsma = get_max_sma(system)
    for star in system.xpath('.//star'):
        t = [' ']*80
        d = [' ']*80
        d[0] = spectral_colorize('*', star)
        t[0] = star.find('name').text[-1]
        for planet in star.iterfind('planet'):
            sma = float(planet.find('semimajoraxis').text)
            loc = int((sma / maxsma) * 78) + 1
            while t[loc] != ' ':
                loc += 1
            t[loc] = planet_name(planet)
            d[loc] = get_planet_size(planet).value
        names.append(''.join(t))
        dots.append(''.join(d))
    result = []
    for idx in range(len(dots)):
        result.append(dots[idx])
        result.append(names[idx])
    return '\n'.join(result)

def tweet_system(system):
    """Return system summary suitable for a twitter message.

    :param system: lxml etree based on <system> tag.
    """
    s = []
    for star in system.iterfind('star'):
        s.append(star.find('name').text)
        for planet in star.iterfind('planet'):
            planet_size = get_planet_size(planet)
            s.append('  ' + planet_size.value + ' ' + planet_name(planet))
    return '\n'.join(s)

def get_system_names(tree):
    """Returns list of all system names.

    :param tree: Tree to find systems in.
    """
    names = [n.replace(' ','_') for n in tree.xpath('./system/name/text()')]
    return names

class PlanetCmd(cmd.Cmd):
    def __init__(self, system_names):
        cmd.Cmd.__init__(self)
        self.prompt = '> '
        self.intro = "Exoplanet Explorer (type help commands):"
        self.system_names = system_names

    def do_most_recent_planet(self, args):
        planet = most_recent_planet(tree)
        print "Most recently updated planet is " + str(planet.find('name').text)
        print "  updated on " + planet.find('lastupdate').text
        print ""
        print planet.find('description').text

    def do_most_recent_system(self, args):
        print "Most recently updated system is:"
        most_recent = most_recent_system(tree)
        print summarize_system(most_recent)

    def do_stats(self, args):
        print "Catalog contains " + str(num_planets(tree)) + " planets in " + str(num_systems(tree)) + " systems."

    def do_largest_system(self, args):
        print "Largest system is:"
        largest = largest_system(tree)
        #write_tree(largest,'largest.xml')
        print summarize_system(largest)

    def do_system(self, system_name):
        print summarize_system(find_system_by_name(system_name))

    def do_tweet(self, system_name):
        print tweet_system(find_system_by_name(system_name))

    def complete_tweet(self, text, line, begidx, endidx):
        return self.complete_system(text, line, begidx, endidx)

    def complete_system(self, text, line, begidx, endidx):
        if not text:
            completions = self.system_names[:]
        else:
            completions = [ f
                            for f in self.system_names
                            if f.lower().startswith(text.lower())
                            ]
        return completions

    def do_exit(self, args):
        exit()

if __name__ == '__main__':
    tree = get_etree()
    system_names = get_system_names(tree)
    PlanetCmd(system_names).cmdloop()

<commit_msg>Fixed Kepler-47 error when mass isn't known.<commit_after>#!/usr/bin/python
import sys
from lxml import etree
import urllib
import gzip
import io
import time
import cmd

# TODO
# Some systems have more than one name
# Kepler-47 (circumbinary) has an error I think because there's no mass
# There's something weird with the system name autocomplete

try:
    from enum import Enum
except ImportError:
    print "You need the enum backport."
    print "pip install enum34"

try:
    from colorama import Fore, Back, Style, init
    init()
except ImportError:
    # colorama support is optional.
    pass

class PlanetSize(Enum):
    """Defines fuzzy planet size labels.

    The enum value is an ASCII depiction."""
    terrestrial = '.'
    neptune = 'o'
    jupiter = '@'
    unknown = '?'

#Constants
JUPITER_IN_EARTH_MASSES = 317.828
NEPTUNE_IN_EARTH_MASSES = 17.147
JUPITER_IN_EARTH_RADII = 11.209 #equatorial
NEPTUNE_IN_EARTH_RADII = 3.883 #equatorial

SYMBOL = {'sun': u'\u2609',
          'earth': u'\u2295',
          'neptune': u'\u2646',
          'jupiter': u'\u2643'}

if not sys.modules.has_key('colorama'):
    # Doesn't wreck things if colorama isn't installed.
    STARCOLOR = {'O': '',
                 'B': '',
                 'A': '',
                 'F': '',
                 'G': '',
                 'K': '',
                 'M': ''}
    RSTCOLOR = ''
else:
    STARCOLOR = {'O': Fore.MAGENTA + Style.BRIGHT,
                 'B': Fore.BLUE + Style.BRIGHT,
                 'A': Fore.WHITE + Style.BRIGHT,
                 'F': Fore.YELLOW + Style.BRIGHT,
                 'G': Fore.YELLOW + Style.BRIGHT,
                 'K': Fore.YELLOW,
                 'M': Fore.RED + Style.BRIGHT}
    RSTCOLOR = Fore.RESET + Style.RESET_ALL

def demo():
    # Output mass and radius of all planets
    for planet in oec.findall(".//planet"):
        print [planet.findtext("mass"), planet.findtext("radius")]

    # Find all circumbinary planets
    for planet in oec.findall(".//binary/planet"):
        print planet.findtext("name")

    # Output distance to planetary system (in pc, if known) and number of planets in system
    for system in oec.findall(".//system"):
        print system.findtext("distance"), len(system.findall(".//planet"))

def get_planet_radius(planet, multiple='jupiter'):
    """Returns planet radius as a float times Jupiter Radii

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    radius_node = planet.find('radius')
    if radius_node is None:
        return None
    jup_radii = float(radius_node.text)
    if multiple == 'jupiter':
        return jup_radii
    elif multiple == 'neptune':
        return (jup_radii * JUPITER_IN_EARTH_RADII) / NEPTUNE_IN_EARTH_RADII
    elif multiple == 'earth':
        return jup_radii * JUPITER_IN_EARTH_RADII

def get_planet_mass(planet, multiple='jupiter'):
    """Returns planet mass as a float times mass of multiple.

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    if planet.find('mass') is not None:
        jup_mass = float(planet.find('mass').text)
    else:
        return None
    if multiple == 'jupiter':
        return jup_mass
    elif multiple == 'neptune':
        return (jup_mass * JUPITER_IN_EARTH_MASSES) / NEPTUNE_IN_EARTH_MASSES
    elif multiple == 'earth':
        return jup_mass * JUPITER_IN_EARTH_MASSES

def get_planet_size(planet):
    """Returns PlanetSize enum for planet.

    Makes determination based on radius and mass criteria.
    :param planet: lxml etree node for <planet>
    """
    earth_radii = get_planet_radius(planet, 'earth')
    if earth_radii is not None:
        if earth_radii < 2:
            return PlanetSize.terrestrial
        elif earth_radii > 7:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    else:
        earth_masses = get_planet_mass(planet, 'earth')
        if earth_masses < 10:
            return PlanetSize.terrestrial
        elif earth_masses > 30:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    return PlanetSize.unknown

def get_etree():
    url = "https://github.com/OpenExoplanetCatalogue/oec_gzip/raw/master/systems.xml.gz"
    fo = io.BytesIO(urllib.urlopen(url).read())
    tree = etree.parse(gzip.GzipFile(fileobj=fo))
    return tree

def find_system_by_name(name):
    """Returns system node matching supplied name

    :param name: string containing desired system name.
    """
    system_name = name.replace('_',' ')
    target = tree.xpath('.//system/name[text()="'+system_name+'"]')
    return get_parent_tag(target[0], 'system')

def most_recent_planet(tree):
    """Returns planet node that has most recent update date.

    :param tree: lxml etree
    :returns: lxml etree
    """
    updates = tree.findall('.//lastupdate')
    update_time = [time.strptime(date.text,'%y/%m/%d') for date in updates]
    planet = updates[update_time.index(max(update_time))].getparent()
    return planet

def most_recent_system(tree):
    """Returns system with most recently updated planet.

    :param tree: lxml etree
    """
    planet = most_recent_planet(tree)
    return get_parent_tag(planet, 'system')

def get_parent_tag(node, tag):
    """Traverses up the tree from node until it finds tag and then returns that node.

    :param node: The child node to start the search from
    :param tag: String with tag type we're searching for.
    """
    if node.tag == tag:
        return node
    else:
        return get_parent_tag(node.getparent(), tag)

def num_planets(tree):
    """Returns the total number of planets in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//planet)"))

def num_stars(tree):
    """Returns the total number of stars in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//star)"))

def num_systems(tree):
    """Returns the total number of systems in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//system)"))

def largest_system(tree):
    """Returns system node that has the largest number of planets.

    :param tree: lxml etree
    :returns: lxml etree centered on system with the mode planets.
    """
    system_size = [num_planets(system) for system in tree.xpath(".//system")]
    largest_idx = system_size.index(max(system_size))
    root = tree.xpath("//systems")[0]
    return root[largest_idx]

def write_tree(tree,fn):
    xmlstr = etree.tostring(tree, pretty_print=True)
    with open(fn, 'w') as f:
        f.write(xmlstr)

def planet_name(planet):
    """Returns name of planet, just letter.
    """
    return planet.find('name').text.split(' ')[-1]

def spectral_colorize(s, star):
    """Returns string s with spectral color colorama codes
    """
    spectral = star.find('spectraltype').text
    stellarclass = spectral[0]
    if stellarclass in STARCOLOR.keys():
        return STARCOLOR[stellarclass] + s + RSTCOLOR
    else:
        return s

def spectral_name(star):
    """Retruns string containing spectral name of star, with colorama color

    Doesn't include ANSII code if colorama is not installed.
    :param star: lxml etree on <star> node
    """
    spectral = star.find('spectraltype').text
    return spectral_colorize(spectral, star)

def summarize_star(star):
    """return one line summary of star"""
    if star.find('name').text[-2] == ' ':
        name = star.find('name').text[-1]
    else:
        name = ' '
    return u'{0} {1} {2}{3}'.format(name, spectral_name(star), star.find('mass').text, 'M'+SYMBOL['sun'])

def format_planet_mass_str(planet):
    """Takes float multiple of Jupiter mass, and returns it rounded and prefixed.

    Uses J2.3 for jupiter masses.
    Uses E3.4 for earth masses if under 0.05 jupiter masses.
    """
    jup_mass = get_planet_mass(planet, 'jupiter')
    if jup_mass is None:
        return u'  ?M{}'.format(SYMBOL['jupiter'])
    else:
        planet_size = get_planet_size(planet)
        flt_mass = float(planet.find('mass').text)
        if planet_size.name == 'terrestrial':
            use_mass = get_planet_mass(planet, 'earth')
            symbol = SYMBOL['earth']
        else:
            use_mass = jup_mass
            symbol = SYMBOL['jupiter']
        return u'{:0.3f}M{}'.format(round(use_mass, 3), symbol)

def summarize_planet(planet):
    """Return one line summary of planet"""
    if planet.find('list').text == "Confirmed planets":
        reliable = ' '
    else:
        reliable = '?'

    letter = planet_name(planet)

    mass = format_planet_mass_str(planet)
    return u'{0} {1} {2}'.format(reliable, letter, mass)

def summarize_system(system):
    """Prints concise summary of system represented by tree

    :param system: ommi clxml etree based on <system> tag.
    """
    s = []
    s.append(system.find('name').text + ' - ' + str(num_stars(system)) + ' stars - ' + str(num_planets(system)) + ' planets')
    s.append(ascii_system(system))
    s.append('')
    binary = system.find('binary')
    if binary is not None:
        system = binary
    for star in system.iterfind('star'):
        s.append(' ' + summarize_star(star))
        for planet in star.iterfind('planet'):
            s.append('   ' + summarize_planet(planet))
    if system.find('planet') is not None:
        s.append(' (..) circumbinary planets:')
    for planet in system.iterfind('planet'):
        s.append('   ' + summarize_planet(planet))
    return '\n'.join(s)

def get_max_sma(tree):
    """Returns max Semi-Major Axis (AU) for all planets in tree.

    :param tree: An lxml etree
    """
    allsmas = [float(x.text) for x in tree.findall('.//semimajoraxis')]
    return max(allsmas)

def ascii_system(system):
    """Return an ASCII graphic of the system.

    :param system: lxml etree based on <system> tag.
    """
    dots = []
    names = []
    maxsma = get_max_sma(system)
    for star in system.xpath('.//star'):
        t = [' ']*80
        d = [' ']*80
        d[0] = spectral_colorize('*', star)
        t[0] = star.find('name').text[-1]
        for planet in star.iterfind('planet'):
            sma = float(planet.find('semimajoraxis').text)
            loc = int((sma / maxsma) * 78) + 1
            while t[loc] != ' ':
                loc += 1
            t[loc] = planet_name(planet)
            d[loc] = get_planet_size(planet).value
        names.append(''.join(t))
        dots.append(''.join(d))
    result = []
    for idx in range(len(dots)):
        result.append(dots[idx])
        result.append(names[idx])
    return '\n'.join(result)

def tweet_system(system):
    """Return system summary suitable for a twitter message.

    :param system: lxml etree based on <system> tag.
    """
    s = []
    for star in system.iterfind('star'):
        s.append(star.find('name').text)
        for planet in star.iterfind('planet'):
            planet_size = get_planet_size(planet)
            s.append('  ' + planet_size.value + ' ' + planet_name(planet))
    return '\n'.join(s)

def get_system_names(tree):
    """Returns list of all system names.

    :param tree: Tree to find systems in.
    """
    names = [n.replace(' ','_') for n in tree.xpath('./system/name/text()')]
    return names

class PlanetCmd(cmd.Cmd):
    def __init__(self, system_names):
        cmd.Cmd.__init__(self)
        self.prompt = '> '
        self.intro = "Exoplanet Explorer (type help commands):"
        self.system_names = system_names

    def do_most_recent_planet(self, args):
        planet = most_recent_planet(tree)
        print "Most recently updated planet is " + str(planet.find('name').text)
        print "  updated on " + planet.find('lastupdate').text
        print ""
        print planet.find('description').text

    def do_most_recent_system(self, args):
        print "Most recently updated system is:"
        most_recent = most_recent_system(tree)
        print summarize_system(most_recent)

    def do_stats(self, args):
        print "Catalog contains " + str(num_planets(tree)) + " planets in " + str(num_systems(tree)) + " systems."

    def do_largest_system(self, args):
        print "Largest system is:"
        largest = largest_system(tree)
        #write_tree(largest,'largest.xml')
        print summarize_system(largest)

    def do_system(self, system_name):
        print summarize_system(find_system_by_name(system_name))

    def do_tweet(self, system_name):
        print tweet_system(find_system_by_name(system_name))

    def complete_tweet(self, text, line, begidx, endidx):
        return self.complete_system(text, line, begidx, endidx)

    def complete_system(self, text, line, begidx, endidx):
        if not text:
            completions = self.system_names[:]
        else:
            completions = [ f
                            for f in self.system_names
                            if f.lower().startswith(text.lower())
                            ]
        return completions

    def do_exit(self, args):
        exit()

if __name__ == '__main__':
    tree = get_etree()
    system_names = get_system_names(tree)
    PlanetCmd(system_names).cmdloop()

<|endoftext|>


<commit_before>from flask import Flask
from os import environ, mkdir
from os.path import exists,join
from shutil import rmtree
import os.path
#import whoosh
#import whoosh.fields, whoosh.index
import pymongo
from pymongo import MongoClient
import sys

#whoosh_dir = join(environ["OPENSHIFT_DATA_DIR"],'index')
#
#if True or not exists(whoosh_dir):
#    rmtree(whoosh_dir)
#    mkdir(whoosh_dir)
#
#    schema = whoosh.fields.Schema(
#        title = whoosh.fields.TEXT(),
#        authors = whoosh.fields.TEXT(),
#        description = whoosh.fields.TEXT()
#    )
#    ix = whoosh.index.create_in(whoosh_dir, schema)
#else:
#    ix = whoosh.index.open_ix(whoosh_dir)

app = Flask(__name__)

mongoclient = pymongo.MongoClient(environ["OPENSHIFT_MONGODB_DB_URL"])
db = mongoclient.database

@app.route('/eimer/add/<thing>')
def add_to_collection(thing):
    db.eimer.insert({"thing" : thing})
    return "%s added" % thing

@app.route('/eimer/view')
def view_collection():
    return " ".join([doc["thing"] for doc in db.eimer.find()])

if __name__ == '__main__':
    app.run()
<commit_msg>searching<commit_after>from flask import Flask
from os import environ, mkdir
from os.path import exists,join
from shutil import rmtree
import os.path
import whoosh
import whoosh.fields, whoosh.index
from whoosh.query import *
import pymongo
from pymongo import MongoClient
import sys

whoosh_dir = join(environ["OPENSHIFT_DATA_DIR"],'index')

if True or not exists(whoosh_dir):
    rmtree(whoosh_dir)
    mkdir(whoosh_dir)

    schema = whoosh.fields.Schema(
        thing = whoosh.fields.TEXT(),
        subthing = whoosh.fields.TEXT()
    )
    ix = whoosh.index.create_in(whoosh_dir, schema)
else:
    ix = whoosh.index.open_ix(whoosh_dir)

app = Flask(__name__)

mongoclient = pymongo.MongoClient(environ["OPENSHIFT_MONGODB_DB_URL"])
db = mongoclient.database
db.eimer.drop()

@app.route('/eimer/add/<thing>/<subthing>')
def add_to_collection(thing,subthing):
    db.eimer.insert({"thing" : thing, "subthing" : subthing})

    #TODO: handle non-thread safety
    writer = ix.writer()
    writer.add_document(thing=thing, subthing=subthing)
    writer.commit()
    return "%s added" % thing

@app.route('/eimer/view')
def view_collection():
    return " ".join([doc["thing"] for doc in db.eimer.find()])

@app.route('/eimer/search/<term>')
def search_collection(term):
    with ix.searcher() as searcher:
        query = Or([Term("thing",term), Term("subthing",term)])
        results = searcher.search(query)
        if len(results) == 0:
            return "No results found"
        else:
            " ".join(res["thing"] for res in results)

if __name__ == '__main__':
    app.run()
<|endoftext|>


<commit_before>from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_oauthlib.client import OAuth
from flask.ext.login import LoginManager

app = Flask(__name__, static_url_path='')
app.config.from_object('config')

lm = LoginManager()
lm.init_app(app)

db = SQLAlchemy(app)

oauth = OAuth(app)

facebook = oauth.remote_app(
    'facebook',
    consumer_key=app.config.get('FACEBOOK_APP_ID'),
    consumer_secret=app.config.get('FACEBOOK_APP_SECRET'),
    request_token_params={'scope': 'email'},
    base_url='https://graph.facebook.com',
    request_token_url=None,
    access_token_url='/oauth/access_token',
    access_token_method='GET',
    authorize_url='https://www.facebook.com/dialog/oauth'
)

from app import views, models<commit_msg>Add Sentry tracking<commit_after>from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_oauthlib.client import OAuth
from flask.ext.login import LoginManager

from raven.contrib.flask import Sentry

app = Flask(__name__, static_url_path='')
app.config.from_object('config')

sentry = Sentry(app, dsn=app.config.get('SENTRY_DSN'))
sentry.init_app(app)

lm = LoginManager()
lm.init_app(app)

db = SQLAlchemy(app)

oauth = OAuth(app)

facebook = oauth.remote_app(
    'facebook',
    consumer_key=app.config.get('FACEBOOK_APP_ID'),
    consumer_secret=app.config.get('FACEBOOK_APP_SECRET'),
    request_token_params={'scope': 'email'},
    base_url='https://graph.facebook.com',
    request_token_url=None,
    access_token_url='/oauth/access_token',
    access_token_method='GET',
    authorize_url='https://www.facebook.com/dialog/oauth'
)

from app import views, models<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
	app
	~~~~~~~~~~~~~~

	Provides the flask application
"""
import config

from os import getenv
from json import JSONEncoder, dumps
from api import Amazon
from amazon.api import SearchException
from urllib2 import HTTPError
from flask import Flask, redirect, url_for, request, make_response
from flask.ext.cache import Cache

cache = Cache()
search_cache_timeout = 1 * 60 * 60  # hours (in seconds)

def jsonify(status=200, indent=2, sort_keys=True, **kwargs):
	options = {'indent': indent, 'sort_keys': sort_keys}
	response = make_response(dumps(kwargs, cls=CustomEncoder, **options))
	response.headers['Content-Type'] = 'application/json; charset=utf-8'
	response.headers['mimetype'] = 'application/json'
	response.status_code = status
	return response

def corsify(response, methods):
	base = 'HEAD, OPTIONS'
	headers = 'Origin, X-Requested-With, Content-Type, Accept'

	for m in methods:
		base += ', %s' % m

	response.headers['Access-Control-Allow-Origin'] = '*'
	response.headers['Access-Control-Allow-Methods'] = base
	response.headers['Access-Control-Allow-Headers'] = headers
	response.headers['Access-Control-Allow-Credentials'] = 'true'
	return response

def make_cache_key(*args, **kwargs):
	return request.url

def create_app(config_mode=None, config_file=None):
	# Create webapp instance
	app = Flask(__name__)
	cache_config = {}

	if config_mode:
		app.config.from_object(getattr(config, config_mode))
	elif config_file:
		app.config.from_pyfile(config_file)
	else:
		app.config.from_envvar('APP_SETTINGS', silent=True)

	if app.config['HEROKU']:
		cache_config['CACHE_TYPE'] = 'spreadsaslmemcachedcache'
		cache_config['CACHE_MEMCACHED_SERVERS'] = [getenv('MEMCACHIER_SERVERS')]
		cache_config['CACHE_MEMCACHED_USERNAME'] = getenv('MEMCACHIER_USERNAME')
		cache_config['CACHE_MEMCACHED_PASSWORD'] = getenv('MEMCACHIER_PASSWORD')
	elif app.config['DEBUG_MEMCACHE']:
		cache_config['CACHE_TYPE'] = 'memcached'
		cache_config['CACHE_MEMCACHED_SERVERS'] = [getenv('MEMCACHE_SERVERS')]
	else:
		cache_config['CACHE_TYPE'] = 'simple'

	cache.init_app(app, config=cache_config)

	@app.route('/')
	def home():
		return redirect(url_for('api'))

	@app.route('/api/')
	@app.route('%s/' % app.config['API_URL_PREFIX'])
	def api():
		return 'Welcome to the %s!' % app.config['APP_NAME']

	@app.route('/api/search/')
	@app.route('%s/search/' % app.config['API_URL_PREFIX'])
	@cache.cached(timeout=search_cache_timeout, key_prefix=make_cache_key)
	def search():
		kwargs = request.args.to_dict()
		limit = int(kwargs.pop('limit', 1))
		amazon = Amazon(**kwargs)

		new = {
			'Condition': 'New',
			'SearchIndex': 'All',
			'ResponseGroup': 'Medium',
		}

		kwargs.update(new)

		try:
			response = amazon.search_n(limit, **kwargs)
			result = amazon.parse(response)
			status = 200
		except SearchException as err:
			result = err.message
			status = 500
		except HTTPError:
			result = 'Service Unavailable'
			status = 503
			cache.delete(request.url)

		return jsonify(status, objects=result)

	@app.route('/api/reset/')
	@app.route('%s/reset/' % app.config['API_URL_PREFIX'])
	@cache.cached(timeout=search_cache_timeout)
	def reset():
		cache.clear()
		return jsonify(objects="Caches reset")

	@app.after_request
	def add_cors_header(response):
		return corsify(response, app.config['API_METHODS'])

	return app

class CustomEncoder(JSONEncoder):
	def default(self, obj):
		if set(['quantize', 'year']).intersection(dir(obj)):
			return str(obj)
		elif set(['next', 'union']).intersection(dir(obj)):
			return list(obj)
		return JSONEncoder.default(self, obj)
<commit_msg>Add lorum route<commit_after># -*- coding: utf-8 -*-
"""
	app
	~~~~~~~~~~~~~~

	Provides the flask application
"""
import config

from os import getenv
from json import JSONEncoder, dumps
from api import Amazon
from amazon.api import SearchException
from urllib2 import HTTPError
from flask import Flask, redirect, url_for, request, make_response
from flask.ext.cache import Cache
from loremipsum import get_sentences

cache = Cache()
search_cache_timeout = 1 * 60 * 60  # hours (in seconds)

def jsonify(status=200, indent=2, sort_keys=True, **kwargs):
	options = {'indent': indent, 'sort_keys': sort_keys}
	response = make_response(dumps(kwargs, cls=CustomEncoder, **options))
	response.headers['Content-Type'] = 'application/json; charset=utf-8'
	response.headers['mimetype'] = 'application/json'
	response.status_code = status
	return response

def corsify(response, methods):
	base = 'HEAD, OPTIONS'
	headers = 'Origin, X-Requested-With, Content-Type, Accept'

	for m in methods:
		base += ', %s' % m

	response.headers['Access-Control-Allow-Origin'] = '*'
	response.headers['Access-Control-Allow-Methods'] = base
	response.headers['Access-Control-Allow-Headers'] = headers
	response.headers['Access-Control-Allow-Credentials'] = 'true'
	return response

def make_cache_key(*args, **kwargs):
	return request.url

def create_app(config_mode=None, config_file=None):
	# Create webapp instance
	app = Flask(__name__)
	cache_config = {}

	if config_mode:
		app.config.from_object(getattr(config, config_mode))
	elif config_file:
		app.config.from_pyfile(config_file)
	else:
		app.config.from_envvar('APP_SETTINGS', silent=True)

	if app.config['HEROKU']:
		cache_config['CACHE_TYPE'] = 'spreadsaslmemcachedcache'
		cache_config['CACHE_MEMCACHED_SERVERS'] = [getenv('MEMCACHIER_SERVERS')]
		cache_config['CACHE_MEMCACHED_USERNAME'] = getenv('MEMCACHIER_USERNAME')
		cache_config['CACHE_MEMCACHED_PASSWORD'] = getenv('MEMCACHIER_PASSWORD')
	elif app.config['DEBUG_MEMCACHE']:
		cache_config['CACHE_TYPE'] = 'memcached'
		cache_config['CACHE_MEMCACHED_SERVERS'] = [getenv('MEMCACHE_SERVERS')]
	else:
		cache_config['CACHE_TYPE'] = 'simple'

	cache.init_app(app, config=cache_config)

	@app.route('/')
	def home():
		return redirect(url_for('api'))

	@app.route('/api/')
	@app.route('%s/' % app.config['API_URL_PREFIX'])
	def api():
		return 'Welcome to the %s!' % app.config['APP_NAME']

	@app.route('/api/search/')
	@app.route('%s/search/' % app.config['API_URL_PREFIX'])
	@cache.cached(timeout=search_cache_timeout, key_prefix=make_cache_key)
	def search():
		kwargs = request.args.to_dict()
		limit = int(kwargs.pop('limit', 1))
		amazon = Amazon(**kwargs)

		new = {
			'Condition': 'New',
			'SearchIndex': 'All',
			'ResponseGroup': 'Medium',
		}

		kwargs.update(new)

		try:
			response = amazon.search_n(limit, **kwargs)
			result = amazon.parse(response)
			status = 200
		except SearchException as err:
			result = err.message
			status = 500
		except HTTPError:
			result = 'Service Unavailable'
			status = 503
			cache.delete(request.url)

		return jsonify(status, objects=result)

	@app.route('/api/lorum/')
	@app.route('%s/lorum/' % app.config['API_URL_PREFIX'])
	@cache.cached(timeout=search_cache_timeout, key_prefix=make_cache_key)
	def lorum():
		return jsonify(objects=get_sentences(1)[0])

	@app.route('/api/reset/')
	@app.route('%s/reset/' % app.config['API_URL_PREFIX'])
	@cache.cached(timeout=search_cache_timeout)
	def reset():
		cache.clear()
		return jsonify(objects="Caches reset")

	@app.after_request
	def add_cors_header(response):
		return corsify(response, app.config['API_METHODS'])

	return app

class CustomEncoder(JSONEncoder):
	def default(self, obj):
		if set(['quantize', 'year']).intersection(dir(obj)):
			return str(obj)
		elif set(['next', 'union']).intersection(dir(obj)):
			return list(obj)
		return JSONEncoder.default(self, obj)
<|endoftext|>


<commit_before>from datetime import timedelta, datetime

from flask import Flask, request, redirect
from flask.ext.bootstrap import Bootstrap
from flask_wtf.csrf import CsrfProtect
from dmutils import apiclient, init_app, flask_featureflags, formats

from config import configs

bootstrap = Bootstrap()
data_api_client = apiclient.DataAPIClient()
csrf = CsrfProtect()
feature_flags = flask_featureflags.FeatureFlag()

def create_app(config_name):

    application = Flask(__name__,
                        static_folder='static/',
                        static_url_path=configs[config_name].STATIC_URL_PATH)

    init_app(
        application,
        configs[config_name],
        bootstrap=bootstrap,
        data_api_client=data_api_client,
        feature_flags=feature_flags
    )
    # Should be incorporated into digitalmarketplace-utils as well
    csrf.init_app(application)

    from .main import main as main_blueprint
    from .status import status as status_blueprint

    if application.config['AUTHENTICATION']:
        from .main.helpers.auth import requires_auth
        application.permanent_session_lifetime = timedelta(minutes=60)
        main_blueprint.before_request(requires_auth)

    application.register_blueprint(status_blueprint, url_prefix='/admin')
    application.register_blueprint(main_blueprint, url_prefix='/admin')
    main_blueprint.config = application.config.copy()

    @application.before_request
    def remove_trailing_slash():
        if request.path != '/' and request.path.endswith('/'):
            return redirect(request.path[:-1], code=301)

    @application.after_request
    def add_header(response):
        response.headers['X-Frame-Options'] = 'DENY'
        return response

    @application.template_filter('timeformat')
    def timeformat(value):
        return datetime.strptime(
            value, formats.DATETIME_FORMAT).strftime('%H:%M:%S')

    @application.template_filter('dateformat')
    def dateformat(value):
        return datetime.strptime(
            value, formats.DATETIME_FORMAT).strftime('%d/%m/%Y')

    @application.template_filter('displaydateformat')
    def display_date_format(value):
        return value.strftime('%d/%m/%Y')

    return application
<commit_msg>Removed X-Frames options from app, now in utils<commit_after>from datetime import timedelta, datetime

from flask import Flask, request, redirect
from flask.ext.bootstrap import Bootstrap
from flask_wtf.csrf import CsrfProtect
from dmutils import apiclient, init_app, flask_featureflags, formats

from config import configs

bootstrap = Bootstrap()
data_api_client = apiclient.DataAPIClient()
csrf = CsrfProtect()
feature_flags = flask_featureflags.FeatureFlag()

def create_app(config_name):

    application = Flask(__name__,
                        static_folder='static/',
                        static_url_path=configs[config_name].STATIC_URL_PATH)

    init_app(
        application,
        configs[config_name],
        bootstrap=bootstrap,
        data_api_client=data_api_client,
        feature_flags=feature_flags
    )
    # Should be incorporated into digitalmarketplace-utils as well
    csrf.init_app(application)

    from .main import main as main_blueprint
    from .status import status as status_blueprint

    if application.config['AUTHENTICATION']:
        from .main.helpers.auth import requires_auth
        application.permanent_session_lifetime = timedelta(minutes=60)
        main_blueprint.before_request(requires_auth)

    application.register_blueprint(status_blueprint, url_prefix='/admin')
    application.register_blueprint(main_blueprint, url_prefix='/admin')
    main_blueprint.config = application.config.copy()

    @application.before_request
    def remove_trailing_slash():
        if request.path != '/' and request.path.endswith('/'):
            return redirect(request.path[:-1], code=301)

    @application.template_filter('timeformat')
    def timeformat(value):
        return datetime.strptime(
            value, formats.DATETIME_FORMAT).strftime('%H:%M:%S')

    @application.template_filter('dateformat')
    def dateformat(value):
        return datetime.strptime(
            value, formats.DATETIME_FORMAT).strftime('%d/%m/%Y')

    @application.template_filter('displaydateformat')
    def display_date_format(value):
        return value.strftime('%d/%m/%Y')

    return application
<|endoftext|>


<commit_before>import unittest

from varsnap import test

from app import serve

class PageCase(unittest.TestCase):
    def setUp(self) -> None:
        serve.app.config['TESTING'] = True
        self.app = serve.app.test_client()

    def test_index_load(self) -> None:
        self.page_test('/', b'')

    def test_robots_load(self) -> None:
        self.page_test('/robots.txt', b'')

    def test_health_load(self) -> None:
        self.page_test('/health', b'ok')

    def test_sitemap_load(self) -> None:
        self.page_test('/sitemap.xml', b'')

    def test_not_found(self) -> None:
        response = self.app.get('/asdf')
        self.assertEqual(response.status_code, 404)
        self.assertIn(b'Not Found', response.get_data())

    def page_test(self, path: str, string: bytes) -> None:
        response = self.app.get(path)
        self.assertEqual(response.status_code, 200)
        self.assertIn(string, response.get_data())
        response.close()

class TestIntegration(unittest.TestCase):
    def test_varsnap(self) -> None:
        with serve.app.test_request_context():
            matches, logs = test()
        if matches is None:
            raise unittest.case.SkipTest('No Snaps found')  # pragma: no cover
        self.assertTrue(matches, logs)
<commit_msg>Add tests<commit_after>import unittest

from varsnap import test

from app import serve

class PageCase(unittest.TestCase):
    def setUp(self) -> None:
        serve.app.config['TESTING'] = True
        self.app = serve.app.test_client()

    def test_index_load(self) -> None:
        self.page_test('/', b'')

    def test_robots_load(self) -> None:
        self.page_test('/robots.txt', b'')

    def test_security_load(self) -> None:
        self.page_test('/.well-known/security.txt', b'Contact')

    def test_humans_load(self) -> None:
        self.page_test('/humans.txt', b'albertyw')

    def test_health_load(self) -> None:
        self.page_test('/health', b'ok')

    def test_sitemap_load(self) -> None:
        self.page_test('/sitemap.xml', b'')

    def test_not_found(self) -> None:
        response = self.app.get('/asdf')
        self.assertEqual(response.status_code, 404)
        self.assertIn(b'Not Found', response.get_data())

    def page_test(self, path: str, string: bytes) -> None:
        response = self.app.get(path)
        self.assertEqual(response.status_code, 200)
        self.assertIn(string, response.get_data())
        response.close()

class TestIntegration(unittest.TestCase):
    def test_varsnap(self) -> None:
        with serve.app.test_request_context():
            matches, logs = test()
        if matches is None:
            raise unittest.case.SkipTest('No Snaps found')  # pragma: no cover
        self.assertTrue(matches, logs)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# coding=utf-8
from flask import Flask
from flask.ext import restful

app = Flask(__name__)
api = restful.Api(app)

<commit_msg>add api init<commit_after>#!/usr/bin/env python
# coding=utf-8
from flask import Flask
from flask.ext import restful

app = Flask(__name__)
api = restful.Api(app)

from .api import *
<|endoftext|>