<commit_before>import collections
import os
import re

from jira import JIRA, JIRAError, utils
from jira.resources import Attachment
from requests import Request

PADDING = 3
CHARS_LIMIT = 255

def _get_filename(path):
    limit = CHARS_LIMIT - PADDING
    fname = os.path.basename(path)
    filename = fname.split('.')[0]
    filename_extension = fname.split('.')[1:]
    count = (
        len('.'.join(filename_extension).encode('utf-8')) + 1
        if filename_extension
        else 0
    )
    char_limit = 0

    for char in filename:
        count += len(char.encode('utf-8'))
        if count > limit:
            break
        else:
            char_limit += 1

    if not char_limit:
        raise JIRAError('Attachment filename is very long.')

    tmp = [filename[:char_limit]]
    tmp.extend(filename_extension)
    filename = '.'.join(tmp)
    return filename

def _upload_file(manager, issue, upload_file, filename):
    # This method will fix original method jira.JIRA.add_attachment (jira/client.py line 591)
    url = manager._get_url('issue/' + str(issue) + '/attachments')
    files = {
        'file': (filename, upload_file),
    }
    headers = {
        'X-Atlassian-Token': 'nocheck',
    }
    req = Request('POST', url, headers=headers, files=files, auth=manager._session.auth)
    prepped = req.prepare()
    prepped.body = re.sub(
        b'filename=.*', b'filename="%s"\r' % filename.encode('utf-8'), prepped.body
    )
    r = manager._session.send(prepped)

    js = utils.json_loads(r)

    if not js or not isinstance(js, collections.Iterable):
        raise JIRAError("Unable to parse JSON: %s" % js)

    attachment = Attachment(manager._options, manager._session, js[0])

    if attachment.size == 0:
        raise JIRAError(
            "Added empty attachment?!: r: %s\nattachment: %s" % (r, attachment)
        )

    return attachment

def add_attachment(manager, issue, path):
    """
    Replace jira's method 'add_attachment' while don't well fixed this issue
    https://github.com/shazow/urllib3/issues/303
    And we need to set filename limit equaled 252 chars.
    :param manager: [jira.JIRA instance]
    :param issue: [jira.JIRA.resources.Issue instance]
    :param path: [string]
    :return: [jira.JIRA.resources.Attachment instance]
    """
    filename = _get_filename(path)

    with open(path, 'rb') as f:
        return _upload_file(manager, issue, f, filename)

def service_desk(manager, id):
    """In Jira v8.7.1 / SD 4.7.1 a Service Desk ID must be an integer.
    We use a hackish workaround to make it work till Atlassian resolves bug
    https://jira.atlassian.com/browse/JSDSERVER-4877.
    """
    try:
        return manager.service_desk(id)
    except JIRAError as e:
        if 'java.lang.NumberFormatException' in e.text:
            service_desks = [sd for sd in manager.service_desks() if sd.projectKey == 'WAL']
            if len(service_desks):
                return service_desks[0]
            else:
                msg = 'The Service Desk with ID {id} does not exist.'.format(id=id)
                raise JIRAError(text=msg, status_code=404)
        else:
            raise e

JIRA.waldur_add_attachment = add_attachment
JIRA.waldur_service_desk = service_desk
<commit_msg>[WAL-2876] Fix a liner error.<commit_after>import collections
import os
import re

from jira import JIRA, JIRAError, utils
from jira.resources import Attachment
from requests import Request

PADDING = 3
CHARS_LIMIT = 255

def _get_filename(path):
    limit = CHARS_LIMIT - PADDING
    fname = os.path.basename(path)
    filename = fname.split('.')[0]
    filename_extension = fname.split('.')[1:]
    count = (
        len('.'.join(filename_extension).encode('utf-8')) + 1
        if filename_extension
        else 0
    )
    char_limit = 0

    for char in filename:
        count += len(char.encode('utf-8'))
        if count > limit:
            break
        else:
            char_limit += 1

    if not char_limit:
        raise JIRAError('Attachment filename is very long.')

    tmp = [filename[:char_limit]]
    tmp.extend(filename_extension)
    filename = '.'.join(tmp)
    return filename

def _upload_file(manager, issue, upload_file, filename):
    # This method will fix original method jira.JIRA.add_attachment (jira/client.py line 591)
    url = manager._get_url('issue/' + str(issue) + '/attachments')
    files = {
        'file': (filename, upload_file),
    }
    headers = {
        'X-Atlassian-Token': 'nocheck',
    }
    req = Request('POST', url, headers=headers, files=files, auth=manager._session.auth)
    prepped = req.prepare()
    prepped.body = re.sub(
        b'filename=.*', b'filename="%s"\r' % filename.encode('utf-8'), prepped.body
    )
    r = manager._session.send(prepped)

    js = utils.json_loads(r)

    if not js or not isinstance(js, collections.Iterable):
        raise JIRAError("Unable to parse JSON: %s" % js)

    attachment = Attachment(manager._options, manager._session, js[0])

    if attachment.size == 0:
        raise JIRAError(
            "Added empty attachment?!: r: %s\nattachment: %s" % (r, attachment)
        )

    return attachment

def add_attachment(manager, issue, path):
    """
    Replace jira's method 'add_attachment' while don't well fixed this issue
    https://github.com/shazow/urllib3/issues/303
    And we need to set filename limit equaled 252 chars.
    :param manager: [jira.JIRA instance]
    :param issue: [jira.JIRA.resources.Issue instance]
    :param path: [string]
    :return: [jira.JIRA.resources.Attachment instance]
    """
    filename = _get_filename(path)

    with open(path, 'rb') as f:
        return _upload_file(manager, issue, f, filename)

def service_desk(manager, id_or_key):
    """In Jira v8.7.1 / SD 4.7.1 a Service Desk ID must be an integer.
    We use a hackish workaround to make it work till Atlassian resolves bug
    https://jira.atlassian.com/browse/JSDSERVER-4877.
    """
    try:
        return manager.service_desk(id_or_key)
    except JIRAError as e:
        if 'java.lang.NumberFormatException' in e.text:
            service_desks = [
                sd for sd in manager.service_desks() if sd.projectKey == id_or_key
            ]
            if len(service_desks):
                return service_desks[0]
            else:
                msg = 'The Service Desk with ID {id} does not exist.'.format(
                    id=id_or_key
                )
                raise JIRAError(text=msg, status_code=404)
        else:
            raise e

JIRA.waldur_add_attachment = add_attachment
JIRA.waldur_service_desk = service_desk
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2017 KMEE
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
    'name': 'Financial',
    'summary': """
        Financial""",
    'version': '10.0.1.0.0',
    'license': 'AGPL-3',
    'author': 'KMEE,Odoo Community Association (OCA)',
    'website': 'www.kmee.com.br',
    'depends': [
        'base',
        'l10n_br_resource',  # FIXME: Implementar esta funcionalidade no core
        'mail',
        'report_xlsx',
        # 'account_payment_mode' # FIXME: Modulo da OCA/bank-payment in v10
        # Estamos temporariamente dependendo deste módulo pois na v8 o model
        # payment.mode esta no core.
        'account_payment_mode',
        #
        # Em um futuro distante não deveremos mais depender de account, pois os
        # models:
        #   - account.payment.term
        #   - account.payment.mode
        # Estarão em um módulo separado
        #
        # TODO: Criar um PR no core separando models do módulo account
        #
        'account',
    ],
    'external_dependencies': {
        'python': ['html2text'],
    },
    'data': [
        'views/financial_menu.xml',

        'data/financial_document_type_data.xml',
        'data/interest_data.xml',
        # 'data/financial_move_data.xml',

        'wizards/financial_cancel.xml',
        # 'wizards/financial_edit.xml',
        # 'wizards/financial_pay_receive.xml',
        'wizards/report_xlsx_financial_cashflow_wizard_view.xml',
        'wizards/report_xlsx_financial_moves_states_wizard.xml',
        'wizards/report_xlsx_financial_defaults_wizard.xml',
        'wizards/report_xlsx_financial_partner_statement_wizard.xml',

        'views/financial_move_payment_one2many_base_view.xml',
        'views/financial_move_debt_base_view.xml',
        'views/financial_move_debt_2receive_view.xml',
        'views/financial_move_debt_2pay_view.xml',

        'views/financial_move_payment_base_view.xml',
        'views/financial_move_payment_receipt_item_view.xml',
        'views/financial_move_payment_payment_item_view.xml',

        'views/financial_installment_base_view.xml',
        'views/financial_installment_2receive_view.xml',
        'views/financial_installment_2pay_view.xml',

        'views/financial_document_type_view.xml',
        'views/financial_account_view.xml',
        'views/inherited_res_partner_bank_view.xml',

        # 'report/financial_cashflow.xml',
        # 'report/financial_statement_report.xml',
        # 'report/report_financial.xml',
        'reports/report_xlsx_financial_cashflow_data.xml',
        'reports/report_xlsx_financial_moves_states_data.xml',

        'reports/report_xlsx_financial_defaults_data.xml',
        'reports/report_xlsx_financial_partner_statement_data.xml',

        # 'security/inherited_res_partner_bank_security.xml',
        'security/ir.model.access.csv',
    ],
    'demo': [
        # 'demo/financial_move.xml',
        # 'demo/financial.account.csv',
        # 'demo/financial_demo.yml'
    ],
    'test': [
        'test/financial_move_test.yml',
    ]
}
<commit_msg>[FEA] Adicionando aviso de módulo deprecado<commit_after># -*- coding: utf-8 -*-
# Copyright 2017 KMEE
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
    'name': '* [OLD] Financial',
    'summary': '''
        * [OLD] Do not install this module, it is here for migration only
        ''',
    'version': '10.0.1.0.0',
    'license': 'AGPL-3',
    'author': 'KMEE,Odoo Community Association (OCA)',
    'website': 'www.kmee.com.br',
    'depends': [
        'base',
        'l10n_br_resource',  # FIXME: Implementar esta funcionalidade no core
        'mail',
        'report_xlsx',
        # 'account_payment_mode' # FIXME: Modulo da OCA/bank-payment in v10
        # Estamos temporariamente dependendo deste módulo pois na v8 o model
        # payment.mode esta no core.
        'account_payment_mode',
        #
        # Em um futuro distante não deveremos mais depender de account, pois os
        # models:
        #   - account.payment.term
        #   - account.payment.mode
        # Estarão em um módulo separado
        #
        # TODO: Criar um PR no core separando models do módulo account
        #
        'account',
    ],
    'external_dependencies': {
        'python': ['html2text'],
    },
    'data': [
        'views/financial_menu.xml',

        'data/financial_document_type_data.xml',
        'data/interest_data.xml',
        # 'data/financial_move_data.xml',

        'wizards/financial_cancel.xml',
        # 'wizards/financial_edit.xml',
        # 'wizards/financial_pay_receive.xml',
        'wizards/report_xlsx_financial_cashflow_wizard_view.xml',
        'wizards/report_xlsx_financial_moves_states_wizard.xml',
        'wizards/report_xlsx_financial_defaults_wizard.xml',
        'wizards/report_xlsx_financial_partner_statement_wizard.xml',

        'views/financial_move_payment_one2many_base_view.xml',
        'views/financial_move_debt_base_view.xml',
        'views/financial_move_debt_2receive_view.xml',
        'views/financial_move_debt_2pay_view.xml',

        'views/financial_move_payment_base_view.xml',
        'views/financial_move_payment_receipt_item_view.xml',
        'views/financial_move_payment_payment_item_view.xml',

        'views/financial_installment_base_view.xml',
        'views/financial_installment_2receive_view.xml',
        'views/financial_installment_2pay_view.xml',

        'views/financial_document_type_view.xml',
        'views/financial_account_view.xml',
        'views/inherited_res_partner_bank_view.xml',

        # 'report/financial_cashflow.xml',
        # 'report/financial_statement_report.xml',
        # 'report/report_financial.xml',
        'reports/report_xlsx_financial_cashflow_data.xml',
        'reports/report_xlsx_financial_moves_states_data.xml',

        'reports/report_xlsx_financial_defaults_data.xml',
        'reports/report_xlsx_financial_partner_statement_data.xml',

        # 'security/inherited_res_partner_bank_security.xml',
        'security/ir.model.access.csv',
    ],
    'demo': [
        # 'demo/financial_move.xml',
        # 'demo/financial.account.csv',
        # 'demo/financial_demo.yml'
    ],
    'test': [
        'test/financial_move_test.yml',
    ]
}
<|endoftext|>


<commit_before>"""Define the main program loop.

This is actually pretty complicated, due to configuration, daemonization, and
restarting options. Here are the objects defined below:

  1. PIDFiler -- as a daemon, manages our pidfile
  2. server_factory -- returns a wsgiserver.CherryPyWSGIServer instance
  3. start_server -- starts the server, with error trapping
  4. drive_daemon -- manipulates Aspen as a daemon
  5. main -- main callable, natch

"""
import base64
import os
import signal
import socket
import sys
import threading
import time
from os.path import isdir, isfile, join

from aspen import mode, restarter
from aspen._configuration import ConfigurationError, Configuration, usage
from aspen.website import Website
from aspen.wsgiserver import CherryPyWSGIServer as Server

if 'win' not in sys.platform:
    from aspen.daemon import Daemon # this actually fails on Windows; needs pwd
else:
    Daemon = None # daemonization not available on Windows; @@: service?

__version__ = '~~VERSION~~'
__all__ = ['configuration', 'conf', 'paths', '']

KILL_TIMEOUT = 5 # seconds between shutdown attempts
PIDCHECK_TIMEOUT = 60 # seconds between pidfile writes

# Configuration API
# =================
# To be populated in server_factory, below.

configuration = None # an aspen._configuration.Configuration instance
conf = None # an aspen._configuration.ConfFile instance
paths = None # an aspen._configuration.Paths instance

class PIDFiler(threading.Thread):
    """Thread to continuously monitor a pidfile, keeping our pid in the file.

    This is run when we are a daemon, in the child process. It checks every
    second to see if the file exists, recreating it if not. It also rewrites the
    file every 60 seconds, in case the contents have changed.

    """

    stop = threading.Event()
    path = '' # path to the pidfile

    def __init__(self):
        threading.Thread.__init__(self)
        self.setDaemon(True)

    def write(self):
        open(self.path, 'w+').write(str(os.getpid()))

    def run(self):
        """Pidfile is initially created and finally destroyed by our Daemon.
        """
        last_pidcheck = 0
        while not self.stop.isSet():
            if not isfile(self.path):
                print "no pidfile; recreating"
                sys.stdout.flush()
                self.write()
            elif (last_pidcheck + PIDCHECK_TIMEOUT) < time.time():
                self.write()
                last_pidcheck = time.time()
            time.sleep(1)
        if isfile(self.path): # sometimes we beat handlesigterm
            os.remove(self.path)

pidfiler = PIDFiler() # must actually set pidfiler.path before starting

globals_ = globals()
def server_factory(configuration):
    """This is the heavy work of instantiating the server.
    """

    # Construct the server.
    # =====================
    # This is done in such a way that user modules may get the already-
    # initialized Configuration, ConfFile, and Paths objects by doing:
    #
    #   from aspen import configuration, conf, paths

    global globals_
    globals_['configuration'] = configuration
    globals_['conf'] = configuration.conf
    globals_['paths'] = configuration.paths
    configuration.load_plugins() # user modules loaded here
    website = Website(configuration)
    for app in configuration.middleware:
        website = app(website)
    server = Server(configuration.address, website, configuration.threads)

    # Monkey-patch server to support restarting.
    # ==========================================
    # Giving server a chance to shutdown cleanly largely avoids the terminal
    # screw-up bug that plagued httpy < 1.0.

    if restarter.CHILD:
        def tick():
            Server.tick(server)
            if restarter.should_restart():
                print "restarting ..."
                server.stop()
                raise SystemExit(75)
        server.tick = tick

    return server

def start_server(configuration):
    """Get a server object and start it up.
    """

    server = server_factory(configuration) # factored out to ease testing

    try:
        print "aspen starting on %s" % str(configuration.address)
        sys.stdout.flush()
        server.start()
    finally:
        print "stopping server"
        sys.stdout.flush()
        server.stop()
        if configuration.sockfam == socket.AF_UNIX:         # clean up socket
            try:
                os.remove(configuration.address)
            except EnvironmentError, exc:
                print "error removing socket:", exc.strerror
        if pidfiler.isAlive():                              # we're a daemon
            pidfiler.stop.set()
            pidfiler.join()

def drive_daemon(configuration):
    """Manipulate a daemon or become one ourselves.
    """

    # Locate some paths.
    # ==================

    __ = join(configuration.paths.root, '__')
    if isdir(__):
        var = join(__, 'var')
        if not isdir(var):
            os.mkdir(var)
        pidfile = join(var, 'aspen.pid')
        logpath = join(var, 'aspen.log')
    else:
        key = ' '.join([str(configuration.address), configuration.paths.root])
        key = base64.urlsafe_b64encode(key)
        pidfile = os.sep + join('tmp', 'aspen-%s.pid' % key)
        logpath = '/dev/null'

    # Instantiate the daemon.
    # =======================

    daemon = Daemon(stdout=logpath, stderr=logpath, pidfile=pidfile)

    # Start/stop wrappers
    # ===================

    def start():
        daemon.start()
        pidfiler.path = pidfile
        pidfiler.start()
        start_server(configuration)

    def stop(stop_output=True):

        # Get the pid.
        # ============

        if not isfile(pidfile):
            print "daemon not running"
            raise SystemExit(1)
        data = open(pidfile).read()
        try:
            pid = int(data)
        except ValueError:
            print "mangled pidfile: '%r'" % data
            raise SystemExit(1)

        # Try pretty hard to kill the process nicely.
        # ===========================================
        # We send two SIGTERMs and a SIGKILL before quitting. The daemon gets
        # 5 seconds after each to shut down.

        def kill(sig):
            try:
                os.kill(pid, sig)
            except OSError, exc:
                print str(exc)
                raise SystemExit(1)

        nattempts = 0
        while isfile(pidfile):

            if nattempts == 0:
                kill(signal.SIGTERM)
            elif nattempts == 1:
                print "%d still going; resending SIGTERM" % pid
                kill(signal.SIGTERM)
            elif nattempts == 2:
                print "%d STILL going; sending SIGKILL and quiting" % pid
                kill(signal.SIGKILL)
                raise SystemExit(1)
            nattempts += 1

            last_attempt = time.time()
            while 1:
                if not isfile(pidfile):
                    return # daemon has stopped
                elif (last_attempt + KILL_TIMEOUT) < time.time():
                    break # daemon hasn't stopped; time to escalate
                else:
                    time.sleep(0.2)

    # Branch
    # ======

    if configuration.command == 'start':
        if isfile(pidfile):
            print "pidfile already exists with pid %s" % open(pidfile).read()
            raise SystemExit(1)
        start()

    elif configuration.command == 'status':
        if isfile(pidfile):
            pid = int(open(pidfile).read())
            command = "ps auxww|grep ' %d .*aspen'|grep -v grep" % pid
            # @@: I, um, doubt this command is portable. :^)
            os.system(command)
            raise SystemExit(0)
        else:
            print "daemon not running"
            raise SystemExit(0)

    elif configuration.command == 'stop':
        stop()
        raise SystemExit(0)

    elif configuration.command == 'restart':
        stop()
        start()

def main(argv=None):
    """Initial phase of configuration, and daemon/restarter/server branch.
    """

    if argv is None:
        argv = sys.argv[1:]

    try:
        configuration = Configuration(argv)
    except ConfigurationError, err:
        print usage
        print err.msg
        raise SystemExit(2)

    try:
        if configuration.daemon:
            drive_daemon(configuration)
        elif mode.DEBDEV and restarter.PARENT:
            print 'starting child server'
            restarter.launch_child()
        else:
            print 'starting server'
            start_server(configuration)
    except KeyboardInterrupt:
        pass
<commit_msg>issue 46: Windows portability niggle<commit_after>"""Define the main program loop.

This is actually pretty complicated, due to configuration, daemonization, and
restarting options. Here are the objects defined below:

  1. PIDFiler -- as a daemon, manages our pidfile
  2. server_factory -- returns a wsgiserver.CherryPyWSGIServer instance
  3. start_server -- starts the server, with error trapping
  4. drive_daemon -- manipulates Aspen as a daemon
  5. main -- main callable, natch

"""
import base64
import os
import signal
import socket
import sys
import threading
import time
from os.path import isdir, isfile, join

from aspen import mode, restarter
from aspen._configuration import ConfigurationError, Configuration, usage
from aspen.website import Website
from aspen.wsgiserver import CherryPyWSGIServer as Server

if 'win' not in sys.platform:
    from aspen.daemon import Daemon # this actually fails on Windows; needs pwd
else:
    WINDOWS = True
    Daemon = None # daemonization not available on Windows; @@: service?

__version__ = '~~VERSION~~'
__all__ = ['configuration', 'conf', 'paths', '']

KILL_TIMEOUT = 5 # seconds between shutdown attempts
PIDCHECK_TIMEOUT = 60 # seconds between pidfile writes

# Configuration API
# =================
# To be populated in server_factory, below.

configuration = None # an aspen._configuration.Configuration instance
conf = None # an aspen._configuration.ConfFile instance
paths = None # an aspen._configuration.Paths instance

class PIDFiler(threading.Thread):
    """Thread to continuously monitor a pidfile, keeping our pid in the file.

    This is run when we are a daemon, in the child process. It checks every
    second to see if the file exists, recreating it if not. It also rewrites the
    file every 60 seconds, in case the contents have changed.

    """

    stop = threading.Event()
    path = '' # path to the pidfile

    def __init__(self):
        threading.Thread.__init__(self)
        self.setDaemon(True)

    def write(self):
        open(self.path, 'w+').write(str(os.getpid()))

    def run(self):
        """Pidfile is initially created and finally destroyed by our Daemon.
        """
        last_pidcheck = 0
        while not self.stop.isSet():
            if not isfile(self.path):
                print "no pidfile; recreating"
                sys.stdout.flush()
                self.write()
            elif (last_pidcheck + PIDCHECK_TIMEOUT) < time.time():
                self.write()
                last_pidcheck = time.time()
            time.sleep(1)
        if isfile(self.path): # sometimes we beat handlesigterm
            os.remove(self.path)

pidfiler = PIDFiler() # must actually set pidfiler.path before starting

globals_ = globals()
def server_factory(configuration):
    """This is the heavy work of instantiating the server.
    """

    # Construct the server.
    # =====================
    # This is done in such a way that user modules may get the already-
    # initialized Configuration, ConfFile, and Paths objects by doing:
    #
    #   from aspen import configuration, conf, paths

    global globals_
    globals_['configuration'] = configuration
    globals_['conf'] = configuration.conf
    globals_['paths'] = configuration.paths
    configuration.load_plugins() # user modules loaded here
    website = Website(configuration)
    for app in configuration.middleware:
        website = app(website)
    server = Server(configuration.address, website, configuration.threads)

    # Monkey-patch server to support restarting.
    # ==========================================
    # Giving server a chance to shutdown cleanly largely avoids the terminal
    # screw-up bug that plagued httpy < 1.0.

    if restarter.CHILD:
        def tick():
            Server.tick(server)
            if restarter.should_restart():
                print "restarting ..."
                server.stop()
                raise SystemExit(75)
        server.tick = tick

    return server

def start_server(configuration):
    """Get a server object and start it up.
    """

    server = server_factory(configuration) # factored out to ease testing

    try:
        print "aspen starting on %s" % str(configuration.address)
        sys.stdout.flush()
        server.start()
    finally:
        print "stopping server"
        sys.stdout.flush()
        server.stop()
        if not WINDOWS:
            if configuration.sockfam == socket.AF_UNIX:     # clean up socket
                try:
                    os.remove(configuration.address)
                except EnvironmentError, exc:
                    print "error removing socket:", exc.strerror
        if pidfiler.isAlive():                              # we're a daemon
            pidfiler.stop.set()
            pidfiler.join()

def drive_daemon(configuration):
    """Manipulate a daemon or become one ourselves.
    """

    # Locate some paths.
    # ==================

    __ = join(configuration.paths.root, '__')
    if isdir(__):
        var = join(__, 'var')
        if not isdir(var):
            os.mkdir(var)
        pidfile = join(var, 'aspen.pid')
        logpath = join(var, 'aspen.log')
    else:
        key = ' '.join([str(configuration.address), configuration.paths.root])
        key = base64.urlsafe_b64encode(key)
        pidfile = os.sep + join('tmp', 'aspen-%s.pid' % key)
        logpath = '/dev/null'

    # Instantiate the daemon.
    # =======================

    daemon = Daemon(stdout=logpath, stderr=logpath, pidfile=pidfile)

    # Start/stop wrappers
    # ===================

    def start():
        daemon.start()
        pidfiler.path = pidfile
        pidfiler.start()
        start_server(configuration)

    def stop(stop_output=True):

        # Get the pid.
        # ============

        if not isfile(pidfile):
            print "daemon not running"
            raise SystemExit(1)
        data = open(pidfile).read()
        try:
            pid = int(data)
        except ValueError:
            print "mangled pidfile: '%r'" % data
            raise SystemExit(1)

        # Try pretty hard to kill the process nicely.
        # ===========================================
        # We send two SIGTERMs and a SIGKILL before quitting. The daemon gets
        # 5 seconds after each to shut down.

        def kill(sig):
            try:
                os.kill(pid, sig)
            except OSError, exc:
                print str(exc)
                raise SystemExit(1)

        nattempts = 0
        while isfile(pidfile):

            if nattempts == 0:
                kill(signal.SIGTERM)
            elif nattempts == 1:
                print "%d still going; resending SIGTERM" % pid
                kill(signal.SIGTERM)
            elif nattempts == 2:
                print "%d STILL going; sending SIGKILL and quiting" % pid
                kill(signal.SIGKILL)
                raise SystemExit(1)
            nattempts += 1

            last_attempt = time.time()
            while 1:
                if not isfile(pidfile):
                    return # daemon has stopped
                elif (last_attempt + KILL_TIMEOUT) < time.time():
                    break # daemon hasn't stopped; time to escalate
                else:
                    time.sleep(0.2)

    # Branch
    # ======

    if configuration.command == 'start':
        if isfile(pidfile):
            print "pidfile already exists with pid %s" % open(pidfile).read()
            raise SystemExit(1)
        start()

    elif configuration.command == 'status':
        if isfile(pidfile):
            pid = int(open(pidfile).read())
            command = "ps auxww|grep ' %d .*aspen'|grep -v grep" % pid
            # @@: I, um, doubt this command is portable. :^)
            os.system(command)
            raise SystemExit(0)
        else:
            print "daemon not running"
            raise SystemExit(0)

    elif configuration.command == 'stop':
        stop()
        raise SystemExit(0)

    elif configuration.command == 'restart':
        stop()
        start()

def main(argv=None):
    """Initial phase of configuration, and daemon/restarter/server branch.
    """

    if argv is None:
        argv = sys.argv[1:]

    try:
        configuration = Configuration(argv)
    except ConfigurationError, err:
        print usage
        print err.msg
        raise SystemExit(2)

    try:
        if configuration.daemon:
            drive_daemon(configuration)
        elif mode.DEBDEV and restarter.PARENT:
            print 'starting child server'
            restarter.launch_child()
        else:
            print 'starting server'
            start_server(configuration)
    except KeyboardInterrupt:
        pass
<|endoftext|>


<commit_before>"""
test_collate_content
====================

Edward J. Stronge
(c) 2014

Tests for the collate_content module
"""
from collections import defaultdict, namedtuple
import os
import random
import tempfile
import shutil
import string
import unittest

from pelican import Pelican
from pelican import ArticlesGenerator, PagesGenerator
from pelican.settings import read_settings

import collate_content as cc

TEMP_PAGE_TEMPLATE = """Title: {title}
Date: 2014-03-06
Category: {category}
Author: Rumpelstiltskin
Tags: just, a, test

some interesting writing!

"""

Content = namedtuple('Content', ['title', 'path', 'category'])
# Characters likely to appear in blog titles/categories. Could eventually
# extend support to more characters that can't appear in a Python identifier
BLOG_CHARACTERS = string.letters + ' -:'

def make_content(directory, categories, count=5, categories_per_content=1):
    """
    make_content --> {(processed_category, original_category): articles, ...}

    Writes random titles and categories into `count` temporary
    files in `directory`. If desired, specify `categories_per_content`
    to assign multiple categories to each written file.

    Returns a dictionary whose keys are in `categories` with values
    that are (title, path, category) tuples for the generated
    content files.
    """
    new_content = defaultdict(list)
    for _ in range(count):
        title = get_random_text_and_whitespace()
        category_choice = random.sample(categories, categories_per_content)
        categories_string = ', '.join(category_choice)
        output = TEMP_PAGE_TEMPLATE.format(
            title=title, category=categories_string)

        content_file = tempfile.NamedTemporaryFile(
            dir=directory, mode='w', suffix='.md', delete=False)
        with content_file as tmp:
            tmp.write(output)
        path = os.path.join(directory, content_file.name)
        for each_cat in category_choice:
            new_content[(cc.substitute_category_name(each_cat), each_cat)]\
                .append(Content(title, path, categories_string))
    return new_content

def get_random_text_and_whitespace(length=10):
    """
    Returns at most `length` randomly-generated letters and/or
    whitespace. The returned string will not begin or end in whitespace.
    """
    return "".join(random.sample(BLOG_CHARACTERS, length)).strip()

def modified_pelican_run(self):
    """Runs the generators and returns the context object

    Modified from the Pelican object's run methods.
    """

    context = self.settings.copy()
    context['filenames'] = {}  # share the dict between all the generators
    context['localsiteurl'] = self.settings['SITEURL']  # share
    generators = [
        cls(
            context=context,
            settings=self.settings,
            path=self.path,
            theme=self.theme,
            output_path=self.output_path,
        ) for cls in self.get_generator_classes()
    ]

    for p in generators:
        if hasattr(p, 'generate_context'):
            p.generate_context()

    writer = self.get_writer()

    for p in generators:
        if hasattr(p, 'generate_output'):
            p.generate_output(writer)

    next(g for g in generators if isinstance(g, ArticlesGenerator))
    next(g for g in generators if isinstance(g, PagesGenerator))
    return context

class TestContentCollation(unittest.TestCase):
    """Test generation of lists of content based on their Category metadata"""

    def setUp(self, settings_overrides=None, articles=5, pages=5,
              categories_per_content=1):
        self.temp_input_dir = tempfile.mkdtemp(prefix="cc-input-")
        page_directory = os.path.join(self.temp_input_dir, 'pages')
        os.mkdir(page_directory)
        self.temp_output_dir = tempfile.mkdtemp(prefix="cc-output-")
        categories = [get_random_text_and_whitespace() for _ in range(5)]
        self.articles = make_content(
            self.temp_input_dir, categories, count=5,
            categories_per_content=categories_per_content)
        self.pages = make_content(
            page_directory, categories, count=5,
            categories_per_content=categories_per_content)
        settings = {
            'PATH': self.temp_input_dir,
            'PAGE_DIR': 'pages',
            'OUTPUT_PATH': self.temp_output_dir,
            'PLUGINS': [cc],
            }
        if settings_overrides is not None:
            settings.update(settings_overrides)
        settings = read_settings(override=settings)
        pelican = Pelican(settings=settings)
        pelican.modified_run = modified_pelican_run
        self.collations = pelican.modified_run(pelican)['collations']

    def tearDown(self):
        shutil.rmtree(self.temp_input_dir)
        shutil.rmtree(self.temp_output_dir)

    def test_articles_with_one_category(self):

        for substituted_category, original_category in self.articles.keys():
            collation_key = '%s_articles' % substituted_category
            self.assertIn(collation_key, self.collations)

            collated_titles = [a.title for a in self.collations[collation_key]]

            for title, _, _ in self.articles[
                    (substituted_category, original_category)]:
                self.assertIn(title, collated_titles)

    def test_pages_with_one_category(self):

        for substituted_category, original_category in self.pages.keys():
            collation_key = '%s_pages' % substituted_category
            self.assertIn(collation_key, self.collations)

            collated_titles = [a.title for a in self.collations[collation_key]]

            for title, _, _ in self.pages[
                    (substituted_category, original_category)]:
                self.assertIn(title, collated_titles)

class TestContentCollationWithFilteredCategories(unittest.TestCase):
    """Test generation of lists of content based on their Category metadata"""

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_articles_with_one_category(self):
        pass

if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromNames(['test_collate_content'])
    unittest.TextTestRunner(verbosity=1).run(suite)
<commit_msg>Reorganized test classes<commit_after>"""
test_collate_content
====================

Edward J. Stronge
(c) 2014

Tests for the collate_content module
"""
from collections import defaultdict, namedtuple
import os
import random
import tempfile
import shutil
import string
import unittest

from pelican import Pelican
from pelican import ArticlesGenerator, PagesGenerator
from pelican.settings import read_settings

import collate_content as cc

TEMP_PAGE_TEMPLATE = """Title: {title}
Date: 2014-03-06
Category: {category}
Author: Rumpelstiltskin
Tags: just, a, test

some interesting writing!

"""

Content = namedtuple('Content', ['title', 'path', 'category'])
# Characters likely to appear in blog titles/categories. Could eventually
# extend support to more characters that can't appear in a Python identifier
BLOG_CHARACTERS = string.letters + ' -:'

def make_content(directory, categories, count=5, categories_per_content=1):
    """
    make_content --> {(processed_category, original_category): articles, ...}

    Writes random titles and categories into `count` temporary
    files in `directory`. If desired, specify `categories_per_content`
    to assign multiple categories to each written file.

    Returns a dictionary whose keys are in `categories` with values
    that are (title, path, category) tuples for the generated
    content files.
    """
    new_content = defaultdict(list)
    for _ in range(count):
        title = get_random_text_and_whitespace()
        category_choice = random.sample(categories, categories_per_content)
        categories_string = ', '.join(category_choice)
        output = TEMP_PAGE_TEMPLATE.format(
            title=title, category=categories_string)

        content_file = tempfile.NamedTemporaryFile(
            dir=directory, mode='w', suffix='.md', delete=False)
        with content_file as tmp:
            tmp.write(output)
        path = os.path.join(directory, content_file.name)
        for each_cat in category_choice:
            new_content[(cc.substitute_category_name(each_cat), each_cat)]\
                .append(Content(title, path, categories_string))
    return new_content

def get_random_text_and_whitespace(length=10):
    """
    Returns at most `length` randomly-generated letters and/or
    whitespace. The returned string will not begin or end in whitespace.
    """
    return "".join(random.sample(BLOG_CHARACTERS, length)).strip()

def modified_pelican_run(self):
    """Runs the generators and returns the context object

    Modified from the Pelican object's run methods.
    """

    context = self.settings.copy()
    context['filenames'] = {}  # share the dict between all the generators
    context['localsiteurl'] = self.settings['SITEURL']  # share
    generators = [
        cls(
            context=context,
            settings=self.settings,
            path=self.path,
            theme=self.theme,
            output_path=self.output_path,
        ) for cls in self.get_generator_classes()
    ]

    for p in generators:
        if hasattr(p, 'generate_context'):
            p.generate_context()

    writer = self.get_writer()

    for p in generators:
        if hasattr(p, 'generate_output'):
            p.generate_output(writer)

    next(g for g in generators if isinstance(g, ArticlesGenerator))
    next(g for g in generators if isinstance(g, PagesGenerator))
    return context

class ContentCollationTester(unittest.TestCase):
    """Test generation of lists of content based on their Category metadata"""

    def setUp(self, settings_overrides=None, articles=5, pages=5,
              categories_per_content=1, categories=None):
        self.temp_input_dir = tempfile.mkdtemp(prefix="cc-input-")
        page_directory = os.path.join(self.temp_input_dir, 'pages')
        os.mkdir(page_directory)
        self.temp_output_dir = tempfile.mkdtemp(prefix="cc-output-")

        if categories is None:
            categories = [get_random_text_and_whitespace() for _ in range(5)]

        self.articles = make_content(
            self.temp_input_dir, categories, count=5,
            categories_per_content=categories_per_content)
        self.pages = make_content(
            page_directory, categories, count=5,
            categories_per_content=categories_per_content)
        settings = {
            'PATH': self.temp_input_dir,
            'PAGE_DIR': 'pages',
            'OUTPUT_PATH': self.temp_output_dir,
            'PLUGINS': [cc],
            }
        if settings_overrides is not None:
            settings.update(settings_overrides)
        settings = read_settings(override=settings)
        pelican = Pelican(settings=settings)
        pelican.modified_run = modified_pelican_run
        self.collations = pelican.modified_run(pelican)['collations']

    def tearDown(self):
        shutil.rmtree(self.temp_input_dir)
        shutil.rmtree(self.temp_output_dir)

class TestContentCollation(ContentCollationTester):
    """Test generation of lists of content based on their Category metadata"""

    def test_articles_with_one_category(self):

        for substituted_category, original_category in self.articles.keys():
            collation_key = '%s_articles' % substituted_category
            self.assertIn(collation_key, self.collations)

            collated_titles = [a.title for a in self.collations[collation_key]]

            for title, _, _ in self.articles[
                    (substituted_category, original_category)]:
                self.assertIn(title, collated_titles)

    def test_pages_with_one_category(self):

        for substituted_category, original_category in self.pages.keys():
            collation_key = '%s_pages' % substituted_category
            self.assertIn(collation_key, self.collations)

            collated_titles = [a.title for a in self.collations[collation_key]]

            for title, _, _ in self.pages[
                    (substituted_category, original_category)]:
                self.assertIn(title, collated_titles)

class TestContentCollationWithFilteredCategories(ContentCollationTester):
    """Test generation of lists of content based on their Category metadata"""

    def setUp(self):
        categories = [get_random_text_and_whitespace() for _ in range(5)]
        self.retained_categories = categories[:2]
        override = {'CATEGORIES_TO_COLLATE': self.retained_categories}

        ContentCollationTester.setUp(
            self, settings_overrides=override, categories=categories)

    def test_articles_with_one_category(self):
        pass

if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromNames(['test_collate_content'])
    unittest.TextTestRunner(verbosity=1).run(suite)
<|endoftext|>


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

"""Test PySlip map-relative text."""

import wx

import pyslip
from osm_tiles import OSMTiles as Tiles

######
# Various demo constants
######

DefaultAppSize = (600, 400)

TileDirectory = './osm_tiles'
MinTileLevel = 0
InitViewLevel = 4
InitViewPosition = (134.0, -25.0)

TextMapData = [(151.20, -33.85, 'Sydney cc', {'placement': 'cc'}),
               (144.95, -37.84, 'Melbourne ne', {'placement': 'ne'}),
               (153.08, -27.48, 'Brisbane ce', {'placement': 'ce'}),
               (115.86, -31.96, 'Perth se', {'placement': 'se'}),
               (138.60, -34.93, 'Adelaide cs', {'placement': 'cs'}),
               (130.83, -12.45, 'Darwin sw', {'placement': 'sw'}),
               (147.33, -42.88, 'Hobart cw', {'placement': 'cw'}),
               (149.20, -35.31, 'Canberra nw', {'placement': 'nw',
                                                'colour': 'red',
                                                'textcolour': 'blue',
                                                'fontsize': 10}),
               (133.90, -23.70, 'Alice Springs cn', {'placement': 'cn'})]

################################################################################
# The main application frame
################################################################################

class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, size=DefaultAppSize,
                          title=('PySlip %s - map-relative text test'
                                 % pyslip.__version__))
        self.SetMinSize(DefaultAppSize)
        self.panel = wx.Panel(self, wx.ID_ANY)
        self.panel.SetBackgroundColour(wx.WHITE)
        self.panel.ClearBackground()

        # create the tile source object
        self.tile_src = Tiles(TileDirectory)

        # build the GUI
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.panel.SetSizer(box)
        self.pyslip = pyslip.PySlip(self.panel, tile_src=self.tile_src,
                                    min_level=MinTileLevel)
        box.Add(self.pyslip, proportion=1, border=1, flag=wx.EXPAND)
        self.panel.SetSizerAndFit(box)
        self.panel.Layout()
        self.Centre()
        self.Show(True)

        # add test test layer
        self.text_layer = self.pyslip.AddTextLayer(TextMapData,
                                                   map_rel=True,
                                                   name='<text_map_layer>',
                                                   offset_x=5, offset_y=1)

        # set initial view position
        self.pyslip.GotoLevelAndPosition(InitViewLevel, InitViewPosition)

#####
# Build the GUI
#####

    def make_gui(self, parent):
        """Create application GUI."""

        # start application layout
        all_display = wx.BoxSizer(wx.HORIZONTAL)
        parent.SetSizer(all_display)
        self.pyslip = pyslip.PySlip(parent, tile_src=self.tile_src,
                                    min_level=MinTileLevel)
        all_display.Add(self.pyslip, proportion=1, border=1, flag=wx.EXPAND)
        parent.SetSizerAndFit(all_display)

    def make_gui_view(self, parent):
        """Build the map view widget

        parent  reference to the widget parent

        Returns the static box sizer.
        """

        # create gui objects
        sb = AppStaticBox(parent, '')
        self.pyslip = pyslip.PySlip(parent, tile_src=self.tile_src,
                                    min_level=MinTileLevel)

        # lay out objects
        box = wx.StaticBoxSizer(sb, orient=wx.HORIZONTAL)
        box.Add(self.pyslip, proportion=1, border=1, flag=wx.EXPAND)

        return box

################################################################################

if __name__ == '__main__':
    import sys
    import traceback

    # our own handler for uncaught exceptions
    def excepthook(type, value, tb):
        msg = '\n' + '=' * 80
        msg += '\nUncaught exception:\n'
        msg += ''.join(traceback.format_exception(type, value, tb))
        msg += '=' * 80 + '\n'
        print msg
        sys.exit(1)

    # plug our handler into the python system
    sys.excepthook = excepthook

    # start wxPython app
    app = wx.App()
    TestFrame().Show()
    app.MainLoop()

<commit_msg>Refined city positions<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Test PySlip map-relative text."""

import wx

import pyslip
from osm_tiles import OSMTiles as Tiles

######
# Various demo constants
######

DefaultAppSize = (600, 400)

TileDirectory = './osm_tiles'
MinTileLevel = 0
InitViewLevel = 4
InitViewPosition = (134.0, -25.0)

# city positions from wikipedia.org
TextMapData = [(151.21, -33.87, 'Sydney cc', {'placement': 'cc'}),
               (144.96, -37.81, 'Melbourne ne', {'placement': 'ne'}),
               (153.03, -27.47, 'Brisbane ce', {'placement': 'ce'}),
               (115.86, -31.95, 'Perth se', {'placement': 'se'}),
               (138.60, -34.93, 'Adelaide cs', {'placement': 'cs'}),
               (130.83, -12.45, 'Darwin sw', {'placement': 'sw'}),
               (147.33, -42.88, 'Hobart cw', {'placement': 'cw'}),
               (149.12, -35.31, 'Canberra nw', {'placement': 'nw',
                                                'colour': 'red',
                                                'textcolour': 'blue',
                                                'fontsize': 10}),
               (133.87, -23.70, 'Alice Springs cn', {'placement': 'cn'})]

################################################################################
# The main application frame
################################################################################

class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, size=DefaultAppSize,
                          title=('PySlip %s - map-relative text test'
                                 % pyslip.__version__))
        self.SetMinSize(DefaultAppSize)
        self.panel = wx.Panel(self, wx.ID_ANY)
        self.panel.SetBackgroundColour(wx.WHITE)
        self.panel.ClearBackground()

        # create the tile source object
        self.tile_src = Tiles(TileDirectory)

        # build the GUI
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.panel.SetSizer(box)
        self.pyslip = pyslip.PySlip(self.panel, tile_src=self.tile_src,
                                    min_level=MinTileLevel)
        box.Add(self.pyslip, proportion=1, border=1, flag=wx.EXPAND)
        self.panel.SetSizerAndFit(box)
        self.panel.Layout()
        self.Centre()
        self.Show(True)

        # add test test layer
        self.text_layer = self.pyslip.AddTextLayer(TextMapData,
                                                   map_rel=True,
                                                   name='<text_map_layer>',
                                                   offset_x=5, offset_y=1)

        # set initial view position
        self.pyslip.GotoLevelAndPosition(InitViewLevel, InitViewPosition)

#####
# Build the GUI
#####

    def make_gui(self, parent):
        """Create application GUI."""

        # start application layout
        all_display = wx.BoxSizer(wx.HORIZONTAL)
        parent.SetSizer(all_display)
        self.pyslip = pyslip.PySlip(parent, tile_src=self.tile_src,
                                    min_level=MinTileLevel)
        all_display.Add(self.pyslip, proportion=1, border=1, flag=wx.EXPAND)
        parent.SetSizerAndFit(all_display)

    def make_gui_view(self, parent):
        """Build the map view widget

        parent  reference to the widget parent

        Returns the static box sizer.
        """

        # create gui objects
        sb = AppStaticBox(parent, '')
        self.pyslip = pyslip.PySlip(parent, tile_src=self.tile_src,
                                    min_level=MinTileLevel)

        # lay out objects
        box = wx.StaticBoxSizer(sb, orient=wx.HORIZONTAL)
        box.Add(self.pyslip, proportion=1, border=1, flag=wx.EXPAND)

        return box

################################################################################

if __name__ == '__main__':
    import sys
    import traceback

    # our own handler for uncaught exceptions
    def excepthook(type, value, tb):
        msg = '\n' + '=' * 80
        msg += '\nUncaught exception:\n'
        msg += ''.join(traceback.format_exception(type, value, tb))
        msg += '=' * 80 + '\n'
        print msg
        sys.exit(1)

    # plug our handler into the python system
    sys.excepthook = excepthook

    # start wxPython app
    app = wx.App()
    TestFrame().Show()
    app.MainLoop()

<|endoftext|>


<commit_before>"""
Code for generating Phy input files:
http://phy-contrib.readthedocs.io/en/latest/template-gui/
"""

from datetime import datetime
import os.path as path

import numpy as np

from ..util import load_asset, load_yaml
from ..geometry import n_steps_neigh_channels
from ..dimensionality_reduction import dimensionality_reduction as dim_red

def params(path_to_config):
    """
    Generate phy's params.py from YASS' config.yaml
    """
    template = load_asset('phy/params.py')
    config = load_yaml(path_to_config)

    timestamp = datetime.now().strftime('%B %-d, %Y at %H:%M')
    dat_path = path.join(config['data']['root_folder'],
                         config['data']['recordings'])
    n_channels_dat = config['recordings']['n_channels']
    dtype = config['recordings']['dtype']
    sample_rate = config['recordings']['sampling_rate']

    params = template.format(timestamp=timestamp,
                             dat_path=dat_path,
                             n_channels_dat=n_channels_dat,
                             dtype=dtype,
                             offset=0,
                             sample_rate=sample_rate,
                             hp_filtered='True')

    return params

def amplitudes(spike_train):
    """
    amplitudes.npy - [nSpikes, ] double vector with the amplitude scaling
    factor that was applied to the template when extracting that spike
    """
    n_spikes, _ = spike_train.shape
    return np.ones(n_spikes)

def channel_map(n_channels):
    """
    Generate channel_map numpy.array. For n_channels, it generates a
    numpy.array as follows: [0, ..., n_channels - 1]
    """
    return np.arange(n_channels)

def pc_feature_ind(n_spikes, n_templates, n_channels, geom, neigh_channels,
                   spike_train, templates):
    """
    pc_feature_ind.npy - [nTemplates, nPCFeatures] uint32 matrix specifying
    which pcFeatures are included in the pc_features matrix.
    """

    # get main channel for each template
    templates_mainc = np.argmax(np.max(templates, axis=1), axis=0)

    # main channel for each spike based on templates_mainc
    spikes_mainc = np.zeros(n_spikes, 'int32')

    for j in range(n_spikes):
        spikes_mainc[j] = templates_mainc[spike_train[j, 1]]

    # number of neighbors to consider
    # NOTE: is the '2' ok to be hardcoded?
    neighbors = n_steps_neigh_channels(neigh_channels, 2)
    nneigh = np.max(np.sum(neighbors, 0))

    # ordered neighboring channels w.r.t. each channel
    c_idx = np.zeros((n_channels, nneigh), 'int32')

    for c in range(n_channels):
        c_idx[c] = (np.argsort(np.sum(np.square(geom - geom[c]), axis=1))
                    [:nneigh])

    pc_feature_ind = np.zeros((n_templates, nneigh), 'int32')

    for k in range(n_templates):
        pc_feature_ind[k] = c_idx[templates_mainc[k]]

    return pc_feature_ind

def similar_templates(templates):
    """
    similar_templates.npy - [nTemplates, nTemplates] single matrix giving the
    similarity score (larger is more similar) between each pair of templates
    """
    _, _, n_templates = templates.shape
    return np.corrcoef(np.reshape(templates, [-1, n_templates]).T)

def template_features(n_spikes, n_templates, n_channels, templates, rotation,
                      score, neigh_channels, geom, spike_train,
                      template_feature_ind_):
    """
    template_features.npy - [nSpikes, nTempFeatures] single matrix giving the
    magnitude of the projection of each spike onto nTempFeatures other
    features. Which other features is specified in template_feature_ind.npy
    """
    C, R2, K = templates.shape
    # R = int((R2 - 1)/2)

    k_neigh = np.min((5, n_templates))

    template_features_ = np.zeros((n_spikes, k_neigh))
    # templates_low_dim = np.zeros((C, rotation.shape[1], K))

    # for k in range(K):
    #     # FIXME: why R:(3*R+1)?
    #     low_dim = np.matmul(templates[:, R:(3*R+1), k], rotation)
    #     templates_low_dim[:, :, k] = low_dim

    rotation = np.transpose(rotation)

    templates_low_dim = np.matmul(rotation, templates)

    # TODO: templates should be rertuned in (n_templates, ... shape)
    templates_low_dim = dim_red.score(templates.T, rotation)

    # TODO: remove repeated code (check: pc_feature_ind)

    # get main channel for each template
    templates_mainc = np.argmax(np.max(templates, axis=1), axis=0)

    # main channel for each spike based on templates_mainc
    spikes_mainc = np.zeros(n_spikes, 'int32')

    for j in range(n_spikes):
        spikes_mainc[j] = templates_mainc[spike_train[j, 1]]

    # number of neighbors to consider
    # NOTE: is the '2' ok to be hardcoded?
    neighbors = n_steps_neigh_channels(neigh_channels, 2)
    nneigh = np.max(np.sum(neighbors, 0))

    # ordered neighboring channels w.r.t. each channel
    c_idx = np.zeros((n_channels, nneigh), 'int32')

    for c in range(n_channels):
        c_idx[c] = (np.argsort(np.sum(np.square(geom - geom[c]), axis=1))
                    [:nneigh])

    for j in range(n_spikes):

        ch_idx = c_idx[spikes_mainc[j]]
        kk = spike_train[j, 1]

        for k in range(k_neigh):
            template_features_[j] = np.sum(
                np.multiply(score[j].T,
                            templates_low_dim[ch_idx]
                            [:, :, template_feature_ind_[kk, k]]))

    return template_features_

def template_feature_ind(n_templates, similar_templates_):
    """
    template_feature_ind.npy - [nTemplates, nTempFeatures] uint32 matrix
    specifying which templateFeatures are included in the template_features
    matrix.
    """
    k_neigh = np.min((5, n_templates))
    template_feature_ind = np.zeros((n_templates, k_neigh), 'int32')

    for k in range(n_templates):
        template_feature_ind[k] = np.argsort(-similar_templates_[k])[:k_neigh]

    return template_feature_ind

def templates_ind(n_templates, n_channels):
    """
    templates_ind.npy - [nTemplates, nTempChannels] double matrix specifying
    the channels on which each template is defined. In the case of Kilosort
    templates_ind is just the integers from 0 to nChannels-1, since templates
    are defined on all channels.
    """
    templates_ind = np.zeros((n_templates, n_channels), 'int32')

    for k in range(n_templates):
        templates_ind[k] = np.arange(n_channels)

    return templates_ind

def whitening_matrices(n_channels):
    """
    whitening_mat.npy - [nChannels, nChannels] double whitening matrix applied
    to the data during automatic spike sorting

    whitening_mat_inv.npy - [nChannels, nChannels] double, the inverse of the
    whitening matrix.
    """
    # return whitening matrix and the inverse
    return np.eye(n_channels), np.eye(n_channels)
<commit_msg>fixes import<commit_after>"""
Code for generating Phy input files:
http://phy-contrib.readthedocs.io/en/latest/template-gui/
"""

from datetime import datetime
import os.path as path

import numpy as np

from ..util import load_asset, load_yaml
from ..geometry import n_steps_neigh_channels
from ..preprocess import dimensionality_reduction as dim_red

def params(path_to_config):
    """
    Generate phy's params.py from YASS' config.yaml
    """
    template = load_asset('phy/params.py')
    config = load_yaml(path_to_config)

    timestamp = datetime.now().strftime('%B %-d, %Y at %H:%M')
    dat_path = path.join(config['data']['root_folder'],
                         config['data']['recordings'])
    n_channels_dat = config['recordings']['n_channels']
    dtype = config['recordings']['dtype']
    sample_rate = config['recordings']['sampling_rate']

    params = template.format(timestamp=timestamp,
                             dat_path=dat_path,
                             n_channels_dat=n_channels_dat,
                             dtype=dtype,
                             offset=0,
                             sample_rate=sample_rate,
                             hp_filtered='True')

    return params

def amplitudes(spike_train):
    """
    amplitudes.npy - [nSpikes, ] double vector with the amplitude scaling
    factor that was applied to the template when extracting that spike
    """
    n_spikes, _ = spike_train.shape
    return np.ones(n_spikes)

def channel_map(n_channels):
    """
    Generate channel_map numpy.array. For n_channels, it generates a
    numpy.array as follows: [0, ..., n_channels - 1]
    """
    return np.arange(n_channels)

def pc_feature_ind(n_spikes, n_templates, n_channels, geom, neigh_channels,
                   spike_train, templates):
    """
    pc_feature_ind.npy - [nTemplates, nPCFeatures] uint32 matrix specifying
    which pcFeatures are included in the pc_features matrix.
    """

    # get main channel for each template
    templates_mainc = np.argmax(np.max(templates, axis=1), axis=0)

    # main channel for each spike based on templates_mainc
    spikes_mainc = np.zeros(n_spikes, 'int32')

    for j in range(n_spikes):
        spikes_mainc[j] = templates_mainc[spike_train[j, 1]]

    # number of neighbors to consider
    # NOTE: is the '2' ok to be hardcoded?
    neighbors = n_steps_neigh_channels(neigh_channels, 2)
    nneigh = np.max(np.sum(neighbors, 0))

    # ordered neighboring channels w.r.t. each channel
    c_idx = np.zeros((n_channels, nneigh), 'int32')

    for c in range(n_channels):
        c_idx[c] = (np.argsort(np.sum(np.square(geom - geom[c]), axis=1))
                    [:nneigh])

    pc_feature_ind = np.zeros((n_templates, nneigh), 'int32')

    for k in range(n_templates):
        pc_feature_ind[k] = c_idx[templates_mainc[k]]

    return pc_feature_ind

def similar_templates(templates):
    """
    similar_templates.npy - [nTemplates, nTemplates] single matrix giving the
    similarity score (larger is more similar) between each pair of templates
    """
    _, _, n_templates = templates.shape
    return np.corrcoef(np.reshape(templates, [-1, n_templates]).T)

def template_features(n_spikes, n_templates, n_channels, templates, rotation,
                      score, neigh_channels, geom, spike_train,
                      template_feature_ind_):
    """
    template_features.npy - [nSpikes, nTempFeatures] single matrix giving the
    magnitude of the projection of each spike onto nTempFeatures other
    features. Which other features is specified in template_feature_ind.npy
    """
    C, R2, K = templates.shape
    # R = int((R2 - 1)/2)

    k_neigh = np.min((5, n_templates))

    template_features_ = np.zeros((n_spikes, k_neigh))
    # templates_low_dim = np.zeros((C, rotation.shape[1], K))

    # for k in range(K):
    #     # FIXME: why R:(3*R+1)?
    #     low_dim = np.matmul(templates[:, R:(3*R+1), k], rotation)
    #     templates_low_dim[:, :, k] = low_dim

    rotation = np.transpose(rotation)

    templates_low_dim = np.matmul(rotation, templates)

    # TODO: templates should be rertuned in (n_templates, ... shape)
    templates_low_dim = dim_red.score(templates.T, rotation)

    # TODO: remove repeated code (check: pc_feature_ind)

    # get main channel for each template
    templates_mainc = np.argmax(np.max(templates, axis=1), axis=0)

    # main channel for each spike based on templates_mainc
    spikes_mainc = np.zeros(n_spikes, 'int32')

    for j in range(n_spikes):
        spikes_mainc[j] = templates_mainc[spike_train[j, 1]]

    # number of neighbors to consider
    # NOTE: is the '2' ok to be hardcoded?
    neighbors = n_steps_neigh_channels(neigh_channels, 2)
    nneigh = np.max(np.sum(neighbors, 0))

    # ordered neighboring channels w.r.t. each channel
    c_idx = np.zeros((n_channels, nneigh), 'int32')

    for c in range(n_channels):
        c_idx[c] = (np.argsort(np.sum(np.square(geom - geom[c]), axis=1))
                    [:nneigh])

    for j in range(n_spikes):

        ch_idx = c_idx[spikes_mainc[j]]
        kk = spike_train[j, 1]

        for k in range(k_neigh):
            template_features_[j] = np.sum(
                np.multiply(score[j].T,
                            templates_low_dim[ch_idx]
                            [:, :, template_feature_ind_[kk, k]]))

    return template_features_

def template_feature_ind(n_templates, similar_templates_):
    """
    template_feature_ind.npy - [nTemplates, nTempFeatures] uint32 matrix
    specifying which templateFeatures are included in the template_features
    matrix.
    """
    k_neigh = np.min((5, n_templates))
    template_feature_ind = np.zeros((n_templates, k_neigh), 'int32')

    for k in range(n_templates):
        template_feature_ind[k] = np.argsort(-similar_templates_[k])[:k_neigh]

    return template_feature_ind

def templates_ind(n_templates, n_channels):
    """
    templates_ind.npy - [nTemplates, nTempChannels] double matrix specifying
    the channels on which each template is defined. In the case of Kilosort
    templates_ind is just the integers from 0 to nChannels-1, since templates
    are defined on all channels.
    """
    templates_ind = np.zeros((n_templates, n_channels), 'int32')

    for k in range(n_templates):
        templates_ind[k] = np.arange(n_channels)

    return templates_ind

def whitening_matrices(n_channels):
    """
    whitening_mat.npy - [nChannels, nChannels] double whitening matrix applied
    to the data during automatic spike sorting

    whitening_mat_inv.npy - [nChannels, nChannels] double, the inverse of the
    whitening matrix.
    """
    # return whitening matrix and the inverse
    return np.eye(n_channels), np.eye(n_channels)
<|endoftext|>


<commit_before>from _pytest.doctest import DoctestModule, DoctestTextfile
import py, pytest

class TestDoctests:

    def test_collect_testtextfile(self, testdir):
        w = testdir.maketxtfile(whatever="")
        checkfile = testdir.maketxtfile(test_something="""
            alskdjalsdk
            >>> i = 5
            >>> i-1
            4
        """)
        for x in (testdir.tmpdir, checkfile):
            #print "checking that %s returns custom items" % (x,)
            items, reprec = testdir.inline_genitems(x)
            assert len(items) == 1
            assert isinstance(items[0], DoctestTextfile)
        items, reprec = testdir.inline_genitems(w)
        assert len(items) == 1

    def test_collect_module(self, testdir):
        path = testdir.makepyfile(whatever="#")
        for p in (path, testdir.tmpdir):
            items, reprec = testdir.inline_genitems(p,
                '--doctest-modules')
            assert len(items) == 1
            assert isinstance(items[0], DoctestModule)

    def test_simple_doctestfile(self, testdir):
        p = testdir.maketxtfile(test_doc="""
            >>> x = 1
            >>> x == 1
            False
        """)
        reprec = testdir.inline_run(p, )
        reprec.assertoutcome(failed=1)

    def test_new_pattern(self, testdir):
        p = testdir.maketxtfile(xdoc ="""
            >>> x = 1
            >>> x == 1
            False
        """)
        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
        reprec.assertoutcome(failed=1)

    def test_doctest_unexpected_exception(self, testdir):
        p = testdir.maketxtfile("""
            >>> i = 0
            >>> 0 / i
            2
        """)
        result = testdir.runpytest("--doctest-modules")
        result.stdout.fnmatch_lines([
            "*unexpected_exception*",
            "*>>> i = 0*",
            "*>>> 0 / i*",
            "*UNEXPECTED*ZeroDivision*",
        ])

    def test_doctest_unex_importerror(self, testdir):
        testdir.tmpdir.join("hello.py").write(py.code.Source("""
            import asdalsdkjaslkdjasd
        """))
        p = testdir.maketxtfile("""
            >>> import hello
            >>>
        """)
        result = testdir.runpytest("--doctest-modules")
        result.stdout.fnmatch_lines([
            "*>>> import hello",
            "*UNEXPECTED*ImportError*",
            "*import asdals*",
        ])

    def test_doctestmodule(self, testdir):
        p = testdir.makepyfile("""
            '''
                >>> x = 1
                >>> x == 1
                False

            '''
        """)
        reprec = testdir.inline_run(p, "--doctest-modules")
        reprec.assertoutcome(failed=1)

    def test_doctestmodule_external_and_issue116(self, testdir):
        p = testdir.mkpydir("hello")
        p.join("__init__.py").write(py.code.Source("""
            def somefunc():
                '''
                    >>> i = 0
                    >>> i + 1
                    2
                '''
        """))
        result = testdir.runpytest(p, "--doctest-modules")
        result.stdout.fnmatch_lines([
            '004 *>>> i = 0',
            '005 *>>> i + 1',
            '*Expected:',
            "*    2",
            "*Got:",
            "*    1",
            "*:5: DocTestFailure"
        ])

    def test_txtfile_failing(self, testdir):
        p = testdir.maketxtfile("""
            >>> i = 0
            >>> i + 1
            2
        """)
        result = testdir.runpytest(p)
        result.stdout.fnmatch_lines([
            '001 >>> i = 0',
            '002 >>> i + 1',
            'Expected:',
            "    2",
            "Got:",
            "    1",
            "*test_txtfile_failing.txt:2: DocTestFailure"
        ])
<commit_msg>fix a buffering issue that i think/hope only occurs during internal tests<commit_after>from _pytest.doctest import DoctestModule, DoctestTextfile
import py, pytest

class TestDoctests:

    def test_collect_testtextfile(self, testdir):
        w = testdir.maketxtfile(whatever="")
        checkfile = testdir.maketxtfile(test_something="""
            alskdjalsdk
            >>> i = 5
            >>> i-1
            4
        """)
        for x in (testdir.tmpdir, checkfile):
            #print "checking that %s returns custom items" % (x,)
            items, reprec = testdir.inline_genitems(x)
            assert len(items) == 1
            assert isinstance(items[0], DoctestTextfile)
        items, reprec = testdir.inline_genitems(w)
        assert len(items) == 1

    def test_collect_module(self, testdir):
        path = testdir.makepyfile(whatever="#")
        for p in (path, testdir.tmpdir):
            items, reprec = testdir.inline_genitems(p,
                '--doctest-modules')
            assert len(items) == 1
            assert isinstance(items[0], DoctestModule)

    def test_simple_doctestfile(self, testdir):
        p = testdir.maketxtfile(test_doc="""
            >>> x = 1
            >>> x == 1
            False
        """)
        reprec = testdir.inline_run(p, )
        reprec.assertoutcome(failed=1)

    def test_new_pattern(self, testdir):
        p = testdir.maketxtfile(xdoc ="""
            >>> x = 1
            >>> x == 1
            False
        """)
        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
        reprec.assertoutcome(failed=1)

    def test_doctest_unexpected_exception(self, testdir):
        p = testdir.maketxtfile("""
            >>> i = 0
            >>> 0 / i
            2
        """)
        result = testdir.runpytest("--doctest-modules")
        result.stdout.fnmatch_lines([
            "*unexpected_exception*",
            "*>>> i = 0*",
            "*>>> 0 / i*",
            "*UNEXPECTED*ZeroDivision*",
        ])

    def test_doctest_unex_importerror(self, testdir):
        testdir.tmpdir.join("hello.py").write(py.code.Source("""
            import asdalsdkjaslkdjasd
        """))
        p = testdir.maketxtfile("""
            >>> import hello
            >>>
        """)
        result = testdir.runpytest("--doctest-modules")
        result.stdout.fnmatch_lines([
            "*>>> import hello",
            "*UNEXPECTED*ImportError*",
            "*import asdals*",
        ])

    def test_doctestmodule(self, testdir):
        p = testdir.makepyfile("""
            '''
                >>> x = 1
                >>> x == 1
                False

            '''
        """)
        reprec = testdir.inline_run(p, "--doctest-modules")
        reprec.assertoutcome(failed=1)

    def test_doctestmodule_external_and_issue116(self, testdir):
        p = testdir.mkpydir("hello")
        p.join("__init__.py").write(py.code.Source("""
            def somefunc():
                '''
                    >>> i = 0
                    >>> i + 1
                    2
                '''
        """))
        result = testdir.runpytest(p, "--doctest-modules")
        result.stdout.fnmatch_lines([
            '004 *>>> i = 0',
            '005 *>>> i + 1',
            '*Expected:',
            "*    2",
            "*Got:",
            "*    1",
            "*:5: DocTestFailure"
        ])

    def test_txtfile_failing(self, testdir):
        p = testdir.maketxtfile("""
            >>> i = 0
            >>> i + 1
            2
        """)
        result = testdir.runpytest(p, "-s")
        result.stdout.fnmatch_lines([
            '001 >>> i = 0',
            '002 >>> i + 1',
            'Expected:',
            "    2",
            "Got:",
            "    1",
            "*test_txtfile_failing.txt:2: DocTestFailure"
        ])
<|endoftext|>


<commit_before>from zeit.cms.i18n import MessageFactory as _
import xml.sax.saxutils
import zc.sourcefactory.source
import zeit.cms.content.sources
import zope.interface
import zope.schema

class IMessage(zope.interface.Interface):

    get_text_from = zope.interface.Attribute(
        'Fieldname from `IPushMessages` to read the text for the notification')

    text = zope.interface.Attribute(
        'Property that can be overriden if `get_text_from` is not sufficient '
        'to retrieve the text for the notification')

    additional_parameters = zope.interface.Attribute(
        'Additional parameters that should be send to `IPushNotifier` as **kw')

    def send():
        """Send push notification to external service via `IPushNotifier`.

        Will fetch the `IPushNotifier` utility using the name that was used to
        register this `IMessage` adapter. Calls the utility providing the
        message config, `additional_parameters`, `text` and a link to context
        as parameters.

        Currently `additional_parameters` is only used for mobile push
        notifications to enrich the parameteres with `teaserTitle`,
        `teaserText`, `teaserSupertitle` and `image_url`. These information are
        read from the context.

        """

class IPushNotifier(zope.interface.Interface):

    def send(text, link, **kw):
        """Sends given ``text`` as a push message through an external service.

        The ``link`` (an URL) will be integrated into the message (how this
        happens depends on the medium, possibilities include appending to the
        text, attaching as metadata, etc.).

        Additional kw parameters:

        * ``type``: Name of the external service.

        * ``enabled``: If the service is enabled.

        * ``channels``: Restrict push notification to users listening to this
          kind of pushes (`News` or `Eilmeldung`). [only `parse`]

        * ``override_text``: Text that should be used instead of the given
          `text` parameter. [only `parse` & `facebook`]

        * ``account``: Send push notification using given account.
          [only `facebook` & `twitter`]

        """

class WebServiceError(Exception):
    """Web service was unable to process a request due to semantic problems.

    For example, a response with HTTP status code "401 Unauthorized" should
    raise this error.

    """

class TechnicalError(Exception):
    """Web service was unable to process a request due to technical errors.

    For example, a response with HTTP status code "500 Server Error" should
    raise this error.

    """

class IPushMessages(zope.interface.Interface):
    """Configures push services that are notified if context is published.

    Available services are stored in `message_config` on checkin of context.
    When the context is published, send a push notification for each stored
    service whose configuration defines it as `enabled` by looking up a named
    `IMessage` adapter that forwards the actual push to an `IPushNotifier`
    utility.

    """

    date_last_pushed = zope.schema.Datetime(
        title=_('Last push'), required=False, readonly=True)

    # BBB deprecated, Facebook texts are now stored per account in
    # message_config.
    long_text = zope.schema.Text(
        title=_('Long push text'), required=False)
    short_text = zope.schema.TextLine(
        title=_('Short push text'),
        required=False,
        # 117 + 1 Space + 23 characters t.co-URL = 140
        #
        # XXX It's not yet clear what we can do when the user enters another
        # URL as part of the tweet and that URL gets *longer* during the
        # shortening process.
        max_length=116)

    """A message configuration is a dict with at least the following keys:
       - type: Kind of service (twitter, facebook, ...). Must correspond
         to the utility name of an IPushNotifier.
       - enabled: Boolean. This allows keeping the message configuration even
         when it should not be used at the moment, e.g. for different text to
         different accounts.

    Any other keys are type-dependent. (A common additional key is ``account``,
    e.g. Twitter and Facebook support posting to different accounts.)

    """
    message_config = zope.schema.Tuple(required=False, default=())

    messages = zope.interface.Attribute(
        'List of IMessage objects, one for each enabled message_config entry')

PARSE_NEWS_CHANNEL = 'parse-channel-news'
PARSE_BREAKING_CHANNEL = 'parse-channel-breaking'

class IPushURL(zope.interface.Interface):
    """Interface to adapt `ICMSContent` to the base URL for push notifications.

    Usually the result is the `uniqueId` of the `ICMSContent`, but this
    interface serves as an extension point for special treatments of certain
    content types, e.g. `zeit.content.link` objects.

    """

class TwitterAccountSource(zeit.cms.content.sources.XMLSource):

    product_configuration = 'zeit.push'
    config_url = 'twitter-accounts'
    attribute = 'name'

    class source_class(zc.sourcefactory.source.FactoredContextualSource):

        @property
        def MAIN_ACCOUNT(self):
            return self.factory.main_account()

    @classmethod
    def main_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['twitter-main-account']

    def isAvailable(self, node, context):
        return (super(TwitterAccountSource, self).isAvailable(node, context)
                and node.get('name') != self.main_account())

    def access_token(self, value):
        tree = self._get_tree()
        nodes = tree.xpath('%s[@%s= %s]' % (
                           self.title_xpath,
                           self.attribute,
                           xml.sax.saxutils.quoteattr(value)))
        if not nodes:
            return (None, None)
        node = nodes[0]
        return (node.get('token'), node.get('secret'))

twitterAccountSource = TwitterAccountSource()

class FacebookAccountSource(zeit.cms.content.sources.XMLSource):

    product_configuration = 'zeit.push'
    config_url = 'facebook-accounts'
    attribute = 'name'

    class source_class(zc.sourcefactory.source.FactoredContextualSource):

        @property
        def MAIN_ACCOUNT(self):
            return self.factory.main_account()

        @property
        def MAGAZIN_ACCOUNT(self):
            return self.factory.magazin_account()

        @property
        def CAMPUS_ACCOUNT(self):
            return self.factory.campus_account()

    @classmethod
    def main_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['facebook-main-account']

    @classmethod
    def magazin_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['facebook-magazin-account']

    @classmethod
    def campus_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['facebook-campus-account']

    def isAvailable(self, node, context):
        return (super(FacebookAccountSource, self).isAvailable(node, context)
                and node.get('name') != self.main_account())

    def access_token(self, value):
        tree = self._get_tree()
        nodes = tree.xpath('%s[@%s= %s]' % (
                           self.title_xpath,
                           self.attribute,
                           xml.sax.saxutils.quoteattr(value)))
        if not nodes:
            return (None, None)
        node = nodes[0]
        return node.get('token')

facebookAccountSource = FacebookAccountSource()

class IAccountData(zope.interface.Interface):
    """Convenicence acess to IPushMessages.message_config entries"""

    facebook_main_enabled = zope.schema.Bool(title=_('Enable Facebook'))
    facebook_main_text = zope.schema.Text(
        title=_('Facebook Main Text'), required=False)

    facebook_magazin_enabled = zope.schema.Bool(
        title=_('Enable Facebook Magazin'))
    facebook_magazin_text = zope.schema.Text(
        title=_('Facebook Magazin Text'), required=False)

    facebook_campus_enabled = zope.schema.Bool(
        title=_('Enable Facebook Campus'))
    facebook_campus_text = zope.schema.Text(
        title=_('Facebook Campus Text'), required=False)

    twitter_main_enabled = zope.schema.Bool(title=_('Enable Twitter'))
    twitter_ressort_enabled = zope.schema.Bool(
        title=_('Enable Twitter Ressort'))
    twitter_ressort = zope.schema.Choice(
        title=_('Additional Twitter'),
        source=twitterAccountSource,
        required=False)

    mobile_text = zope.schema.TextLine(title=_('Mobile title'), required=False)
    mobile_enabled = zope.schema.Bool(title=_('Enable mobile push'))
<commit_msg>Fix docu.<commit_after>from zeit.cms.i18n import MessageFactory as _
import xml.sax.saxutils
import zc.sourcefactory.source
import zeit.cms.content.sources
import zope.interface
import zope.schema

class IMessage(zope.interface.Interface):

    get_text_from = zope.interface.Attribute(
        'Fieldname from `IPushMessages` to read the text for the notification')

    text = zope.interface.Attribute(
        'Property that can be overriden if `get_text_from` is not sufficient '
        'to retrieve the text for the notification')

    additional_parameters = zope.interface.Attribute(
        'Additional parameters that should be send to `IPushNotifier` as **kw')

    def send():
        """Send push notification to external service via `IPushNotifier`.

        Will fetch the `IPushNotifier` utility using the name that was used to
        register this `IMessage` adapter. Calls the utility providing the
        message config, `additional_parameters`, `text` and a link to context
        as parameters.

        Currently `additional_parameters` is only used for mobile push
        notifications to enrich the parameteres with `teaserTitle`,
        `teaserText`, `teaserSupertitle` and `image_url`. These information are
        read from the context.

        """

class IPushNotifier(zope.interface.Interface):

    def send(text, link, **kw):
        """Sends given ``text`` as a push message through an external service.

        The ``link`` (an URL) will be integrated into the message (how this
        happens depends on the medium, possibilities include appending to the
        text, attaching as metadata, etc.).

        Additional kw parameters:

        * ``type``: Name of the external service.

        * ``enabled``: If the service is enabled.

        * ``channels``: Restrict push notification to users listening to this
          kind of pushes (`News` or `Eilmeldung`). [only `mobile`]

        * ``override_text``: Text that should be used instead of the given
          `text` parameter. [only `mobile` & `facebook`]

        * ``account``: Send push notification using given account.
          [only `facebook` & `twitter`]

        """

class WebServiceError(Exception):
    """Web service was unable to process a request due to semantic problems.

    For example, a response with HTTP status code "401 Unauthorized" should
    raise this error.

    """

class TechnicalError(Exception):
    """Web service was unable to process a request due to technical errors.

    For example, a response with HTTP status code "500 Server Error" should
    raise this error.

    """

class IPushMessages(zope.interface.Interface):
    """Configures push services that are notified if context is published.

    Available services are stored in `message_config` on checkin of context.
    When the context is published, send a push notification for each stored
    service whose configuration defines it as `enabled` by looking up a named
    `IMessage` adapter that forwards the actual push to an `IPushNotifier`
    utility.

    """

    date_last_pushed = zope.schema.Datetime(
        title=_('Last push'), required=False, readonly=True)

    # BBB deprecated, Facebook texts are now stored per account in
    # message_config.
    long_text = zope.schema.Text(
        title=_('Long push text'), required=False)
    short_text = zope.schema.TextLine(
        title=_('Short push text'),
        required=False,
        # 117 + 1 Space + 23 characters t.co-URL = 140
        #
        # XXX It's not yet clear what we can do when the user enters another
        # URL as part of the tweet and that URL gets *longer* during the
        # shortening process.
        max_length=116)

    """A message configuration is a dict with at least the following keys:
       - type: Kind of service (twitter, facebook, ...). Must correspond
         to the utility name of an IPushNotifier.
       - enabled: Boolean. This allows keeping the message configuration even
         when it should not be used at the moment, e.g. for different text to
         different accounts.

    Any other keys are type-dependent. (A common additional key is ``account``,
    e.g. Twitter and Facebook support posting to different accounts.)

    """
    message_config = zope.schema.Tuple(required=False, default=())

    messages = zope.interface.Attribute(
        'List of IMessage objects, one for each enabled message_config entry')

PARSE_NEWS_CHANNEL = 'parse-channel-news'
PARSE_BREAKING_CHANNEL = 'parse-channel-breaking'

class IPushURL(zope.interface.Interface):
    """Interface to adapt `ICMSContent` to the base URL for push notifications.

    Usually the result is the `uniqueId` of the `ICMSContent`, but this
    interface serves as an extension point for special treatments of certain
    content types, e.g. `zeit.content.link` objects.

    """

class TwitterAccountSource(zeit.cms.content.sources.XMLSource):

    product_configuration = 'zeit.push'
    config_url = 'twitter-accounts'
    attribute = 'name'

    class source_class(zc.sourcefactory.source.FactoredContextualSource):

        @property
        def MAIN_ACCOUNT(self):
            return self.factory.main_account()

    @classmethod
    def main_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['twitter-main-account']

    def isAvailable(self, node, context):
        return (super(TwitterAccountSource, self).isAvailable(node, context)
                and node.get('name') != self.main_account())

    def access_token(self, value):
        tree = self._get_tree()
        nodes = tree.xpath('%s[@%s= %s]' % (
                           self.title_xpath,
                           self.attribute,
                           xml.sax.saxutils.quoteattr(value)))
        if not nodes:
            return (None, None)
        node = nodes[0]
        return (node.get('token'), node.get('secret'))

twitterAccountSource = TwitterAccountSource()

class FacebookAccountSource(zeit.cms.content.sources.XMLSource):

    product_configuration = 'zeit.push'
    config_url = 'facebook-accounts'
    attribute = 'name'

    class source_class(zc.sourcefactory.source.FactoredContextualSource):

        @property
        def MAIN_ACCOUNT(self):
            return self.factory.main_account()

        @property
        def MAGAZIN_ACCOUNT(self):
            return self.factory.magazin_account()

        @property
        def CAMPUS_ACCOUNT(self):
            return self.factory.campus_account()

    @classmethod
    def main_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['facebook-main-account']

    @classmethod
    def magazin_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['facebook-magazin-account']

    @classmethod
    def campus_account(cls):
        config = zope.app.appsetup.product.getProductConfiguration(
            cls.product_configuration)
        return config['facebook-campus-account']

    def isAvailable(self, node, context):
        return (super(FacebookAccountSource, self).isAvailable(node, context)
                and node.get('name') != self.main_account())

    def access_token(self, value):
        tree = self._get_tree()
        nodes = tree.xpath('%s[@%s= %s]' % (
                           self.title_xpath,
                           self.attribute,
                           xml.sax.saxutils.quoteattr(value)))
        if not nodes:
            return (None, None)
        node = nodes[0]
        return node.get('token')

facebookAccountSource = FacebookAccountSource()

class IAccountData(zope.interface.Interface):
    """Convenicence acess to IPushMessages.message_config entries"""

    facebook_main_enabled = zope.schema.Bool(title=_('Enable Facebook'))
    facebook_main_text = zope.schema.Text(
        title=_('Facebook Main Text'), required=False)

    facebook_magazin_enabled = zope.schema.Bool(
        title=_('Enable Facebook Magazin'))
    facebook_magazin_text = zope.schema.Text(
        title=_('Facebook Magazin Text'), required=False)

    facebook_campus_enabled = zope.schema.Bool(
        title=_('Enable Facebook Campus'))
    facebook_campus_text = zope.schema.Text(
        title=_('Facebook Campus Text'), required=False)

    twitter_main_enabled = zope.schema.Bool(title=_('Enable Twitter'))
    twitter_ressort_enabled = zope.schema.Bool(
        title=_('Enable Twitter Ressort'))
    twitter_ressort = zope.schema.Choice(
        title=_('Additional Twitter'),
        source=twitterAccountSource,
        required=False)

    mobile_text = zope.schema.TextLine(title=_('Mobile title'), required=False)
    mobile_enabled = zope.schema.Bool(title=_('Enable mobile push'))
<|endoftext|>