<commit_before>from colorama import Fore
from plugin import plugin, require
import requests
import json
import os

@require(network=True)
@plugin("cocktail cookbook")
class Cocktail:
    def __call__(self, jarvis, s):
        self.jarvis = jarvis
        self.main()

    def __init__(self):
        # get the width of terminal
        self.SCREEN_WIDTH = os.get_terminal_size().columns
        self.RECIPE_WIDTH = 60
        self.MARGIN = round((self.SCREEN_WIDTH - self.RECIPE_WIDTH) / 2) + 1
        self.ingredients = [
            "Rum",
            "Light rum",
            "Dark rum",
            "Spiced rum",
            "Añejo rum",
            "Vodka",
            "Gin",
            "Tequila",
            "Whiskey",
            "Blended whiskey",
            "Irish whiskey",
            "Bourbon",
            "Sweet Vermouth",
            "Dry Vermouth",
            "Triple sec",
            "Brandy",
            "Amaretto",
            "Coffee liqueur",
            "Kahlua",
            "Cognac",
            "Ouzo",
            "Irish cream",
            "Sambuca",
            "Creme de Cassis"
        ]

    def main(self):
        """
        Method demonstrate the functionality of Cocktail cookbook plugin.
        """

        # Continue as the user wants more cocktails
        while True:

            self.display_data(self.ingredients)

            # User chooses an ingredient
            selected_ingredient = self.get_input(
                "Base Ingredient", len(self.ingredients))

            if self.is_exit_input(selected_ingredient):
                break
            # List of cocktails that needs the given ingredient
            retrived_cocktails = self.get_cocktails_by_ingredient(
                selected_ingredient - 1)

            self.display_data(retrived_cocktails)

            chosen_cocktail = self.get_input(
                "Cocktail", len(retrived_cocktails))

            if self.is_exit_input(selected_ingredient):
                break
            # List of all ingredients that a cocktail needs
            retrieved_cocktail_ingredients = self.get_cocktail_ingredients(
                chosen_cocktail - 1, retrived_cocktails)
            cocktail = retrived_cocktails[chosen_cocktail - 1]
            self.display_cocktail_ingredients(
                retrieved_cocktail_ingredients, cocktail)

            retrived_cocktail_instructions = self.get_cocktail_instructions(
                chosen_cocktail - 1, retrived_cocktails)

            self.display_cocktail_instructions(retrived_cocktail_instructions)

            if self.is_end():
                break

    def get_json(self, URL: str):
        """
        Return data from given url in json format
        """
        try:
            request = requests.get(URL)
        except Exception:
            self.jarvis.say(
                "Can not reach URL for the moment.")
        return json.loads(request.text)

    def display_data(self, data):
        """
        Display available data to the user.
        """
        # data is a list of str
        for number, d in enumerate(data):
            self.jarvis.say(
                f'{(number + 1):{3}}.  {d}')

    def display_cocktail_ingredients(self, ingredients, cocktail):
        """
        Display Cocktail ingredients to the user
        """

        self.header_msg(f"ALL YOU NEED for {cocktail}")

        self.jarvis.say(f'{"INGREDIENTS":{30}}{"MEASURE":{15}}'.center(
            self.SCREEN_WIDTH), color=Fore.LIGHTGREEN_EX)
        for ingredient in ingredients:
            self.jarvis.say(
                f'{ingredient[0] :{30}}{ingredient[1]:{15}}'.center(self.SCREEN_WIDTH))

    def display_cocktail_instructions(self, instructions: str):
        """
        Display Cocktail ingredients to the user
        """
        self.header_msg("HOW TO MAKE IT")
        instructions = instructions.replace("\r\n", " ")

        for line in range(0, len(instructions), self.RECIPE_WIDTH):
            # skip space in the end of the line
            if instructions[line] == " " and line < len(instructions):
                line += 1
            self.jarvis.say(
                f'{" " * self.MARGIN}{instructions[line : line + self.RECIPE_WIDTH]}')

    def header_msg(self, msg: str):
        self.jarvis.say(
            f'{"-" * self.RECIPE_WIDTH}'.center(self.SCREEN_WIDTH))
        self.jarvis.say(f"{msg}".center(
            self.SCREEN_WIDTH))
        self.jarvis.say(
            f'{"-" * self.RECIPE_WIDTH}'.center(self.SCREEN_WIDTH))

    def get_input(self, input_is: str, upper_bound: int):
        """
        Get user input and validate it.
        Input must be a number that corresponds to an ingredients.
        """
        input_code = None
        # Until country code is valid
        while not input_code:
            try:
                input_code = self.jarvis.input(
                    f"Choose {input_is}: ", color=Fore.GREEN)
                if self.is_valid_input(int(input_code), upper_bound):
                    raise ValueError
            except ValueError:
                if self.is_exit_input(input_code):
                    return 'exit'
                self.jarvis.say(
                    f"Please select a number (1 - {upper_bound})", color=Fore.YELLOW)
                input_code = None
        return int(input_code)

    def is_exit_input(self, input):
        if (type(input) == str and input.lower() == "exit"):
            return True

    def is_valid_input(self, input, upper_bound):
        if type(input) == int and self.is_out_of_range(input, upper_bound):
            return True

    def is_out_of_range(self, x: int, upper_bound) -> bool:
        return (True if (x > upper_bound or x <= 0) else False)

    def is_end(self):
        inp = self.jarvis.input(
            "Do you want to continue? (Y/N) ", color=Fore.RED)
        if inp.lower() == "n" or inp.lower() == "exit":
            return True

    def get_cocktail_ingredients(self, cocktail: int, cocktails):
        """
        Get chosen cocktail's ingredients.
        """
        URL = f"https://www.thecocktaildb.com/api/json/v1/1/search.php?s={cocktails[cocktail]}"
        json_text = self.get_json(URL)['drinks'][0]
        cocktail_ingredients = []
        for i in range(1, 16):
            ingredient = json_text[f'strIngredient{i}']
            measure = json_text[f'strMeasure{i}']
            if ingredient:
                if not measure:
                    cocktail_ingredients.append([ingredient, "Your pref."])
                else:
                    cocktail_ingredients.append([ingredient, measure])
        return cocktail_ingredients

    def get_cocktail_instructions(self, cocktail: int, cocktails):
        """
        Get chosen cocktail's instructions.
        """
        URL = f"https://www.thecocktaildb.com/api/json/v1/1/search.php?s={cocktails[cocktail]}"
        json_text = self.get_json(URL)['drinks'][0]["strInstructions"]
        return json_text

    def get_cocktails_by_ingredient(self, ingredients: int):
        """
        Get most popular cocktails with the given ingredients as base one.
        """
        URL = f"https://www.thecocktaildb.com/api/json/v1/1/filter.php?i={self.ingredients[ingredients]}"
        json_text = self.get_json(URL)["drinks"]
        cocktails = [i["strDrink"] for i in json_text]
        return cocktails
<commit_msg>Add alias to cocktail.py<commit_after>from colorama import Fore
from plugin import plugin, require, alias
import requests
import json
import os

@require(network=True)
@alias("cocktails")
@plugin("cocktail cookbook")
class Cocktail:
    def __call__(self, jarvis, s):
        self.jarvis = jarvis
        self.main()

    def __init__(self):
        # get the width of terminal
        self.SCREEN_WIDTH = os.get_terminal_size().columns
        self.RECIPE_WIDTH = 60
        self.MARGIN = round((self.SCREEN_WIDTH - self.RECIPE_WIDTH) / 2) + 1
        self.ingredients = [
            "Rum",
            "Light rum",
            "Dark rum",
            "Spiced rum",
            "Añejo rum",
            "Vodka",
            "Gin",
            "Tequila",
            "Whiskey",
            "Blended whiskey",
            "Irish whiskey",
            "Bourbon",
            "Sweet Vermouth",
            "Dry Vermouth",
            "Triple sec",
            "Brandy",
            "Amaretto",
            "Coffee liqueur",
            "Kahlua",
            "Cognac",
            "Ouzo",
            "Irish cream",
            "Sambuca",
            "Creme de Cassis"
        ]

    def main(self):
        """
        Method demonstrate the functionality of Cocktail cookbook plugin.
        """

        # Continue as the user wants more cocktails
        while True:

            self.display_data(self.ingredients)

            # User chooses an ingredient
            selected_ingredient = self.get_input(
                "Base Ingredient", len(self.ingredients))

            if self.is_exit_input(selected_ingredient):
                break
            # List of cocktails that needs the given ingredient
            retrived_cocktails = self.get_cocktails_by_ingredient(
                selected_ingredient - 1)

            self.display_data(retrived_cocktails)

            chosen_cocktail = self.get_input(
                "Cocktail", len(retrived_cocktails))

            if self.is_exit_input(selected_ingredient):
                break
            # List of all ingredients that a cocktail needs
            retrieved_cocktail_ingredients = self.get_cocktail_ingredients(
                chosen_cocktail - 1, retrived_cocktails)
            cocktail = retrived_cocktails[chosen_cocktail - 1]
            self.display_cocktail_ingredients(
                retrieved_cocktail_ingredients, cocktail)

            retrived_cocktail_instructions = self.get_cocktail_instructions(
                chosen_cocktail - 1, retrived_cocktails)

            self.display_cocktail_instructions(retrived_cocktail_instructions)

            if self.is_end():
                break

    def get_json(self, URL: str):
        """
        Return data from given url in json format
        """
        try:
            request = requests.get(URL)
        except Exception:
            self.jarvis.say(
                "Can not reach URL for the moment.")
        return json.loads(request.text)

    def display_data(self, data):
        """
        Display available data to the user.
        """
        # data is a list of str
        for number, d in enumerate(data):
            self.jarvis.say(
                f'{(number + 1):{3}}.  {d}')

    def display_cocktail_ingredients(self, ingredients, cocktail):
        """
        Display Cocktail ingredients to the user
        """

        self.header_msg(f"ALL YOU NEED for {cocktail}")

        self.jarvis.say(f'{"INGREDIENTS":{30}}{"MEASURE":{15}}'.center(
            self.SCREEN_WIDTH), color=Fore.LIGHTGREEN_EX)
        for ingredient in ingredients:
            self.jarvis.say(
                f'{ingredient[0] :{30}}{ingredient[1]:{15}}'.center(self.SCREEN_WIDTH))

    def display_cocktail_instructions(self, instructions: str):
        """
        Display Cocktail ingredients to the user
        """
        self.header_msg("HOW TO MAKE IT")
        instructions = instructions.replace("\r\n", " ")

        for line in range(0, len(instructions), self.RECIPE_WIDTH):
            # skip space in the end of the line
            if instructions[line] == " " and line < len(instructions):
                line += 1
            self.jarvis.say(
                f'{" " * self.MARGIN}{instructions[line : line + self.RECIPE_WIDTH]}')

    def header_msg(self, msg: str):
        self.jarvis.say(
            f'{"-" * self.RECIPE_WIDTH}'.center(self.SCREEN_WIDTH))
        self.jarvis.say(f"{msg}".center(
            self.SCREEN_WIDTH))
        self.jarvis.say(
            f'{"-" * self.RECIPE_WIDTH}'.center(self.SCREEN_WIDTH))

    def get_input(self, input_is: str, upper_bound: int):
        """
        Get user input and validate it.
        Input must be a number that corresponds to an ingredients.
        """
        input_code = None
        # Until country code is valid
        while not input_code:
            try:
                input_code = self.jarvis.input(
                    f"Choose {input_is}: ", color=Fore.GREEN)
                if self.is_valid_input(int(input_code), upper_bound):
                    raise ValueError
            except ValueError:
                if self.is_exit_input(input_code):
                    return 'exit'
                self.jarvis.say(
                    f"Please select a number (1 - {upper_bound})", color=Fore.YELLOW)
                input_code = None
        return int(input_code)

    def is_exit_input(self, input):
        if (type(input) == str and input.lower() == "exit"):
            return True

    def is_valid_input(self, input, upper_bound):
        if type(input) == int and self.is_out_of_range(input, upper_bound):
            return True

    def is_out_of_range(self, x: int, upper_bound) -> bool:
        return (True if (x > upper_bound or x <= 0) else False)

    def is_end(self):
        inp = self.jarvis.input(
            "Do you want to continue? (Y/N) ", color=Fore.RED)
        if inp.lower() == "n" or inp.lower() == "exit":
            return True

    def get_cocktail_ingredients(self, cocktail: int, cocktails):
        """
        Get chosen cocktail's ingredients.
        """
        URL = f"https://www.thecocktaildb.com/api/json/v1/1/search.php?s={cocktails[cocktail]}"
        json_text = self.get_json(URL)['drinks'][0]
        cocktail_ingredients = []
        for i in range(1, 16):
            ingredient = json_text[f'strIngredient{i}']
            measure = json_text[f'strMeasure{i}']
            if ingredient:
                if not measure:
                    cocktail_ingredients.append([ingredient, "Your pref."])
                else:
                    cocktail_ingredients.append([ingredient, measure])
        return cocktail_ingredients

    def get_cocktail_instructions(self, cocktail: int, cocktails):
        """
        Get chosen cocktail's instructions.
        """
        URL = f"https://www.thecocktaildb.com/api/json/v1/1/search.php?s={cocktails[cocktail]}"
        json_text = self.get_json(URL)['drinks'][0]["strInstructions"]
        return json_text

    def get_cocktails_by_ingredient(self, ingredients: int):
        """
        Get most popular cocktails with the given ingredients as base one.
        """
        URL = f"https://www.thecocktaildb.com/api/json/v1/1/filter.php?i={self.ingredients[ingredients]}"
        json_text = self.get_json(URL)["drinks"]
        cocktails = [i["strDrink"] for i in json_text]
        return cocktails
<|endoftext|>


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

"""
    it is a simple script to creat a content for github readme.md
    Usage: python mk-dir-for-md.py RAW_PAGE_FORM_GITHUB
"""

__author__ = "winston"

import urllib2
import sys
import re

def getWebFile(url):
   response = urllib2.urlopen(url)
   return response
def valueToUrl(value):
    ''' change the content vaule to url , so we can link
        " " -> "-"
        "-" or "_" -> same
        "~`!@#$%^&*()+={}[]|\:;"'<>,.?/" -> ""
        full-width only "-" is not change, others -> ""
    '''
    repalceChar = ['~','`','!','@','#','$','%','^','&','*',
            '(',')','+','=','{','}','[',']','|','\\',':',';',
            '\"','\'','<',',','>','.','?','/',
            '·','！','￥','……','（','）','——','【','】','、','：',
            '；','“','‘','《','》','，','。','？']
    value = value.strip()
    for singleChar in repalceChar:
        value = value.replace(singleChar, '')
    # remove the leading and trailing whitespace
    value = value.replace(' ','-')
    return value.lower()

def getSameContentNum(content, urlValue):
    ''' github use the -num to solve the same content problem
        For example,
        # haha -> haha
        # haha -> haha-1
    '''
    num = 0
    if not urlValue:
        num = 1
    for line in content:
        left = line.rfind("#") + 1
        right = line.rfind(")")
        key = line[ left : right ]
        if key == urlValue:
            num = num + 1
        if re.match(r'.*-\d$', key):
            realKey = key[:-2]
            if urlValue == realKey:
                num = int(key[-1]) + 1
    return num

def makeDirFromRawPage( page, isPrint = 1):
    ''' page is a file-like objext'''
    InCode = "```"
    contentTarget  = "#"
    isInCode = 0
    content =[]
    for line in page.readlines():
        if line.count(InCode):
            if isInCode:
                isIndCode = 0
            else:
                isIndCode = 1
        if not isInCode and line[0] == contentTarget:
            count = line.find(" ")
            value = line[count + 1 : -1]
            # "# #" is used to back to top
            # empty string can not work
            if value == contentTarget or not value:
                continue
            # change content value to url
            urlValue = valueToUrl(value)
            # same content
            num = getSameContentNum( content, urlValue)
            if num:
                urlValue += ("-" + str(num))
            contentValue = (count - 1) * "\t" + " [%s](#%s)" % (value , urlValue)
            if isPrint:
                print contentValue
            content.append(contentValue)
    return content

if __name__=='__main__':
    script, url = sys.argv
    result = getWebFile(url)
    content = makeDirFromRawPage(result)

<commit_msg>Updates language/python/practise/mkdirForMd_v1.0.py<commit_after>#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""
    it is a simple script to creat a content for github readme.md
    Usage: python mk-dir-for-md.py RAW_PAGE_FORM_GITHUB
"""

__author__ = "winston"

import urllib2
import sys
import re

def getWebFile(url):
   response = urllib2.urlopen(url)
   return response
def valueToUrl(value):
    ''' change the content vaule to url , so we can link
        " " -> "-"
        "-" or "_" -> same
        "~`!@#$%^&*()+={}[]|\:;"'<>,.?/" -> ""
        full-width only "-" is not change, others -> ""
    '''
    repalceChar = ['~','`','!','@','#','$','%','^','&','*',
            '(',')','+','=','{','}','[',']','|','\\',':',';',
            '\"','\'','<',',','>','.','?','/',
            '·','！','￥','……','（','）','——','【','】','、','：',
            '；','“','‘','《','》','，','。','？']
    value = value.strip()
    for singleChar in repalceChar:
        value = value.replace(singleChar, '')
    # remove the leading and trailing whitespace
    value = value.replace(' ','-')
    return value.lower()

def getSameContentNum(content, urlValue):
    ''' github use the -num to solve the same content problem
        For example,
        # haha -> haha
        # haha -> haha-1
    '''
    num = 0
    if not urlValue:
        num = 1
    for line in content:
        left = line.rfind("#") + 1
        right = line.rfind(")")
        key = line[ left : right ]
        if key == urlValue:
            num = num + 1
        if re.match(r'.*-\d$', key):
            realKey = key[:-2]
            if urlValue == realKey:
                num = int(key[-1]) + 1
    return num

def makeDirFromRawPage( page, isPrint = 1):
    ''' page is a file-like objext'''
    InCode = "```"
    contentTarget  = "#"
    isInCode = 0
    content =[]
    for line in page.readlines():
        if line.count(InCode):
            if isInCode:
                isIndCode = 0
            else:
                isIndCode = 1
        if not isInCode and line[0] == contentTarget:
            count = line.find(" ")
            value = line[count + 1 : -1]
            # "# #" is used to back to top
            # empty string can not work
            if value == contentTarget or not value:
                continue
            # change content value to url
            urlValue = valueToUrl(value)
            # same content
            num = getSameContentNum( content, urlValue)
            if num:
                urlValue += ("-" + str(num))
            contentValue = (count - 1) * "\t" + "* [%s](#%s)" % (value , urlValue)
            if isPrint:
                print contentValue
            content.append(contentValue)
    return content

if __name__=='__main__':
    script, url = sys.argv
    result = getWebFile(url)
    content = makeDirFromRawPage(result)

<|endoftext|>


<commit_before>''' SmartAPI Data-Aware Tests
    nosetests tests:SmartAPITest
    nosetests tests:SmartAPITestTornadoClient
'''

import json
import os
import re
import sys
import unittest
import nose
import requests
from nose.tools import eq_, ok_
from tornado.web import Application

from biothings.tests.test_helper import (BiothingsTestCase,
                                         TornadoTestServerMixin)
from biothings.web.settings import BiothingESWebSettings

class SmartAPITest(BiothingsTestCase):

    host = os.getenv("SMARTAPI_HOST", "https://smart-api.info")
    host = host.rstrip('/')
    api = host + '/api'

    def test_query_all_has_hits(self):
        self.query_has_hits('__all__')

    def test_query_translator_has_hits(self):
        self.query_has_hits('translator')

    def test_query_by_tags_name_translator_has_hits(self):
        self.query_has_hits('tags.name:translator')

    def test_query_non_exist_special_char_string(self):
        res = self.json_ok(self.get_ok(self.api +
                                       '/query?q=translat\xef\xbf\xbd\xef\xbf\xbd'))
        eq_(res['hits'], [])

    def test_query_string_not_provided(self):
        self.get_status_match(self.api + '/query', 400)

    def test_query_multiple_filters_biothings(self):
        res = self.json_ok(self.get_ok(self.api +
                                       '/query?q=__all__&filters={"tags.name.raw":["annotation","variant"],"info.contact.name.raw":["Chunlei Wu"]}'))
        eq_(len(res['hits']), 3)

    def test_query_specified_fields(self):
        res = self.json_ok(self.get_ok(self.api +
                                       '/query?q=__all__&fields=_id,info'))
        for h in res['hits']:
            self.assertTrue('_id' in h and 'info' in h)

    def test_query_return_raw(self):
        res = self.json_ok(self.get_ok(self.api +
                                       '/query?q=__all__&raw=1'))
        self.assertTrue('_shards' in res)

    def test_query_return_raw_query_match_all(self):
        res = self.json_ok(self.get_ok(self.api +
                                       '/query?q=__all__&rawquery=1'))
        ref_query = {
            "query": {
                "bool": {
                    "must_not": {
                        "term": {
                            "_meta._archived": "true"
                        }
                    },
                    "must": {
                        "match_all": {}
                    }
                }
            }
        }
        self.assertTrue(res == ref_query)

    def test_query_specify_size(self):
        res = self.json_ok(self.get_ok(self.api +
                                       '/query?size=6&q=__all__'))
        eq_(len(res['hits']), 6)

    def test_query_invalid_size(self):
        self.get_status_match(self.api + '/query?q=__all__&size=my', 400)

    def test_query_invalid_from(self):
        res_0 = self.json_ok(self.get_ok(self.api +
                                         '/query?q=__all__&fields=_id&size=5'))
        ids_0 = set([hit['_id'] for hit in res_0['hits']])
        res_1 = self.json_ok(self.get_ok(self.api +
                                         '/query?q=__all__&fields=_id&size=5&from=5'))
        ids_1 = [hit['_id'] for hit in res_1['hits']]
        for _id in ids_1:
            if _id in ids_0:
                self.assertTrue(False)

class SmartAPITestTornadoClient(TornadoTestServerMixin, SmartAPITest):
    '''
        Self contained test class
        Starts a Tornado server and perform tests against this server.
    '''

    @classmethod
    def setup_class(cls):
        ''' Reads Tornado Settings from config.py '''
        cls.WEB_SETTINGS = BiothingESWebSettings(config='config')
        cls.APP_LIST = cls.WEB_SETTINGS.generate_app_list()
        cls.STATIC_PATH = cls.WEB_SETTINGS.STATIC_PATH

    def get_app(self):
        return Application(self.APP_LIST, static_path=self.STATIC_PATH)

# client = SmartAPITestTornadoClient(methodName='test_query_multiple_filters_biothings')
if __name__ == '__main__':
    nose.main()
<commit_msg>Refactor Tests by Groups<commit_after>'''
    SmartAPI Read-Only Test
'''

import os

from nose.core import runmodule
from nose.tools import eq_

from biothings.tests import BiothingsTestCase, TornadoTestServerMixin

class SmartAPIRemoteTest(BiothingsTestCase):

    ''' Test against server specified in environment variable SMARTAPI_HOST
        or SmartAPI production server if SMARTAPI_HOST is not specified '''

    __test__ = True  # explicitly set this to be a test class

    host = os.getenv("SMARTAPI_HOST", "https://smart-api.info")
    api = '/api'

    # Query Functionalities

    def test_101_regular(self):
        ''' Query regular string '''
        self.query(q='translator')

    def test_102_named_field(self):
        ''' Query named field '''
        self.query(q='tags.name:translator')

    def test_103_match_all(self):
        ''' Query all documents '''
        self.query(q='__all__')

    def test_104_random_score(self):
        ''' Query random documents '''
        res = self.query(q='__any__')
        query_1_id = res['hits'][0]['_id']
        res = self.query(q='__any__')
        query_2_id = res['hits'][0]['_id']
        assert query_1_id != query_2_id

    def test_105_filters(self):
        ''' Query with multiple filters '''
        flt = '{"tags.name.raw":["annotation","variant"],"info.contact.name.raw":["Chunlei Wu"]}'
        res = self.query(q='__all__', filters=flt)
        eq_(len(res['hits']), 3)

    # Result Formatting

    def test_201_sources(self):
        ''' Return specified fields '''
        res = self.query(q='__all__', fields='_id,info')
        for hit in res['hits']:
            assert '_id' in hit and 'info' in hit
            assert '_meta' not in hit

    def test_202_size(self):
        ''' Return specified size '''
        res = self.query(q='__all__', size=6)
        eq_(len(res['hits']), 6)

    def test_203_raw(self):
        ''' Return raw ES result '''
        res = self.query(q='__all__', raw=1)
        assert '_shards' in res

    def test_204_query(self):
        ''' Return query sent to ES '''
        res = self.request('query?q=__all__&rawquery=1').json()
        assert "query" in res
        assert "bool" in res["query"]

    # Error Handling

    def test_301_special_char(self):
        ''' Handle special characters '''
        self.query(q='translat\xef\xbf\xbd\xef\xbf\xbd', expect_hits=False)
        self.request("query?q=http://example.com/", expect_status=400)

    def test_302_missing_term(self):
        ''' Handle empty request '''
        self.request("query", expect_status=400)

    def test_303_bad_size(self):
        ''' Handle type error '''
        self.request("query?q=__all__&size=my", expect_status=400)

    def test_304_bad_index(self):
        ''' Handle index out of bound '''
        res_0 = self.request('query?q=__all__&fields=_id&size=5').json()
        ids_0 = {hit['_id'] for hit in res_0['hits']}
        res_1 = self.request('query?q=__all__&fields=_id&size=5&from=5').json()
        ids_1 = [hit['_id'] for hit in res_1['hits']]
        for _id in ids_1:
            if _id in ids_0:
                assert False

class SmartAPILocalTest(TornadoTestServerMixin, SmartAPIRemoteTest):
    '''
        Self contained test class
        Starts a Tornado server and perform tests against this server.
    '''
    __test__ = True  # explicitly set this to be a test class

if __name__ == '__main__':
    runmodule(argv=['', '--logging-level=INFO', '-v'])
<|endoftext|>


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

import os
import sys
import sphinx_rtd_theme
import pbr.version
version_info = pbr.version.VersionInfo('ara')

sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
    'sphinx.ext.autodoc',
]

# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable

# The suffix of source filenames.
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'ara'
copyright = u'2016, Red Hat'
author = 'RDO Community'

# The short X.Y version.
version = version_info.version_string()
# The full version, including alpha/beta/rc tags.
release = version_info.release_string()

# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# -- Options for HTML output --------------------------------------------------

# The theme to use for HTML and HTML Help pages.  Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
    ('index',
     '%s.tex' % project,
     u'%s Documentation' % project,
     u'Red Hat', 'manual'),
]

# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
<commit_msg>Remove some references to the ara project<commit_after># -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys
import sphinx_rtd_theme
import pbr.version
version_info = pbr.version.VersionInfo('novajoin')

sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
    'sphinx.ext.autodoc',
]

# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable

# The suffix of source filenames.
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'novajoin'
copyright = u'2016, Red Hat'
author = 'Rob Crittenden'

# The short X.Y version.
version = version_info.version_string()
# The full version, including alpha/beta/rc tags.
release = version_info.release_string()

# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# -- Options for HTML output --------------------------------------------------

# The theme to use for HTML and HTML Help pages.  Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
    ('index',
     '%s.tex' % project,
     u'%s Documentation' % project,
     u'Red Hat', 'manual'),
]

# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
<|endoftext|>


<commit_before>import networkx as nx
from itertools import combinations
from KCliquePercolation import KCliquePercolation
from ClusterUtility import ClusterUtility

class MaxCliquesPercolation(KCliquePercolation):
    """This class find maximal cliques and their percolation in a graph.

    The procedure will find any intersection (percolation) between any maximal cliques found.
    The cluster is defined as percolated maximal cliques [Reid2012]_.

    References
    ----------
    .. [Reid2012] Fergal Reid, Aaron McDaid, and Neil Hurley. Percolation computation in complex networks.
                  In Proceedings of the 2012 IEEE/ACM International Conference on Advances in Social Networks
                  Analysis and Mining, pp. 274-281, 2012.
    """
    def __init__(self, graph, edges_weight, nodes_id):
        """This is the constructor of class MaxCliquesPercolation

        Parameters
        ----------
        graph           : graph
            Graph to be clustered.
        edges_weight    : list[tuple]
            List of tuple containing (node1, node2, cosine similarity between these two).
        nodes_id        : list
            List of all node identifier.

        Notes
        -----
        max_cliques : list[frozenset]
            List of frozenset containing node id for each maximal clique.
        """
        super(MaxCliquesPercolation, self).__init__(graph, edges_weight, nodes_id)

    def init_maxclique_percolation(self):
        """Initialization of maxial clique percolation method.

        The first step is to build temporary graph (percolation graph). Then the procedure finds
        all maximal cliques in the graph.
        """
        super(MaxCliquesPercolation, self)._build_temp_graph()
        maxcliques = self._find_maxcliques()
        self.cliques = maxcliques

    def get_maxcliques_percolation(self, k):
        """The main method to find clusters based on maximal clique percolation.

        This method looks for percolation between cliques.
        It then remove any edges that connecting two vertices but has different clusters.

        Returns
        -------
        k           : int
            Number of percolation or intersection between an individual clique.
        clusters    : dict[frozenset]
            List of frozenset containing node identifier (node id in integer).
        """
        super(MaxCliquesPercolation, self)._get_percolation_graph(self.cliques, k)
        super(MaxCliquesPercolation, self)._remove_outcluster()
        clusters = super(MaxCliquesPercolation, self)._get_clusters()

        return clusters

    def _find_maxcliques(self):
        """Find maximal cliques using `find_clique` function from NetworkX.

        Returns
        -------
        maxcliques  : list[frozenset]
            List of frozenset containing node id for each maximal clique.
        """
        maxcliques = list(frozenset(c) for c in nx.find_cliques(self.graph))
        self.cliques = maxcliques
        return maxcliques

class MaxCliquesPercolationWeighted(MaxCliquesPercolation):
    """This is a class for maximal clique percolation with edge weight [Liu2009]_.

    Edge weight is evaluated using intensity threshold or the geometric mean
    for all edge weights in a maximal clique [Studiawan2016c]_. We then remove the overlapping nodes
    where a node only follow the weighter neighboring cluster [Studiawan2016c]_.

    References
    ----------
    .. [Liu2009]        Guimei Liu, Limsoon Wong, and Hon Nian Chua. Complex discovery from
                        weighted PPI networks. Bioinformatics, 25(15):1891-1897, 2009.
    .. [Studiawan2016c] H. Studiawan, C. Payne, F. Sohel, SSH log clustering using weighted
                        maximal clique percolation (to be submitted).
    """
    def __init__(self, graph, edges_weight, nodes_id):
        """This is the constructor of class MaxCliquePercolation Weighted.

        The parameters are the same with its parent class but we add a threshold
        for the intensity for maximal clique found.

        Parameters
        ----------
        graph           : graph
            Graph to be clustered.
        edges_weight    : list[tuple]
            List of tuple containing (node1, node2, cosine similarity between these two).
        nodes_id        : list
            List of all node identifier.
        """
        super(MaxCliquesPercolationWeighted, self).__init__(graph, edges_weight, nodes_id)
        self.percolation_dict = {}

    def get_maxcliques_percolation_weighted(self, k, threshold):
        """This is the main method for maximal clique percolation for edge-weighted graph.

        Parameters
        ----------
        k           : int
            Number of percolation or intersection between an individual clique.
        threshold   : float
            Threshold for intensity of maximal clique.

        Returns
        -------
        clusters    : dict[int, frozenset]
            List of frozenset containing node identifier (node id in integer).

        Notes
        -----
        weighted_maxcliques : list[frozenset]
            List of frozenset containing node identifier for each weighted maximal clique.
        """
        # get weighted maximal cliques and get percolation
        weighted_maxcliques = ClusterUtility.get_weighted_cliques(self.graph, self.cliques, threshold)
        super(MaxCliquesPercolationWeighted, self)._get_percolation_graph(weighted_maxcliques, k)
        percolations = self.clique_percolation
        self.__set_percolation_dict(percolations)

        # remove overlapping nodes
        percolations = percolations.values()
        for p1, p2 in combinations(percolations, 2):
            intersections = p1.intersection(p2)
            if intersections:
                for node in intersections:
                    node_neighbors = self.graph.neighbors(node)
                    p1_neighbors, p2_neighbors = p1.intersection(node_neighbors), p2.intersection(node_neighbors)
                    p1_neighbors_weight = self.__get_neighbors_weight(node, p1_neighbors)
                    p2_neighbors_weight = self.__get_neighbors_weight(node, p2_neighbors)

                    # follow the neighboring cluster which has bigger sum of edge-weight
                    self.percolation_dict[node] = self.percolation_dict[list(p1_neighbors)[0]] \
                        if p1_neighbors_weight > p2_neighbors_weight else self.percolation_dict[list(p2_neighbors)[0]]

        self.__set_graph_cluster()
        clusters = self.__get_clusters()
        return clusters

    def __set_percolation_dict(self, percolations):
        """Set dictionary of node id and its index in percolations.

        Parameters
        ----------
        percolations    : dict[frozenset]
            Dictionary of frozenset containing nodes id for each maximal clique.
        """
        nodes = self.graph.nodes()
        percolations_merged = []
        for index, percolation in percolations.iteritems():
            for p in percolation:
                self.percolation_dict[p] = index

            # merge percolations list
            percolations_merged += percolation

        diff = set(nodes).difference(percolations_merged)
        other_cluster = len(percolations)
        if diff:
            for i in diff:
                self.percolation_dict[i] = other_cluster
                other_cluster += 1

    def __get_neighbors_weight(self, node, neighbors):
        """Get all weight of neighboring cluster.

        Parameters
        ----------
        node        : int
            Node identifier
        neighbors   : list[int]
            List of node identifier of intersection between two clusters

        Returns
        -------
        weight  : int
            Sum of all edge weight from specific neighboring cluster.
        """
        weight = 0
        for n in neighbors:
            weight += self.graph[node][n][0]['weight']

        return weight

    def __set_graph_cluster(self):
        """Set cluster id in the given graph based on percolation dictionary.

        Returns
        -------
        self.graph  : graph
            Graph with updated cluster identifier after cluster processing.
        """
        for node in self.graph.nodes_iter(data=True):
            self.graph.node[node[0]]['cluster'] = self.percolation_dict[node[0]]
        return self.graph

    def __get_clusters(self):
        """Get maximal clique percolation as clusters with incremental cluster id.

        Returns
        -------
        clusters    : dict[list]
            Dictionary of list containing node identifier for each cluster found.
        """
        cluster_ids = set(self.percolation_dict.values())
        clusters = {}
        cluster_idx = 0
        for ids in cluster_ids:
            cluster = []
            for node, cluster_id in self.percolation_dict.iteritems():
                if ids == cluster_id:
                    cluster.append(node)
            clusters[cluster_idx] = cluster
            cluster_idx += 1

        return clusters
<commit_msg>Add checking for empty weighted maxcliques and remove percolations variable because it is redundant<commit_after>import networkx as nx
from itertools import combinations
from KCliquePercolation import KCliquePercolation
from ClusterUtility import ClusterUtility

class MaxCliquesPercolation(KCliquePercolation):
    """This class find maximal cliques and their percolation in a graph.

    The procedure will find any intersection (percolation) between any maximal cliques found.
    The cluster is defined as percolated maximal cliques [Reid2012]_.

    References
    ----------
    .. [Reid2012] Fergal Reid, Aaron McDaid, and Neil Hurley. Percolation computation in complex networks.
                  In Proceedings of the 2012 IEEE/ACM International Conference on Advances in Social Networks
                  Analysis and Mining, pp. 274-281, 2012.
    """
    def __init__(self, graph, edges_weight, nodes_id):
        """This is the constructor of class MaxCliquesPercolation

        Parameters
        ----------
        graph           : graph
            Graph to be clustered.
        edges_weight    : list[tuple]
            List of tuple containing (node1, node2, cosine similarity between these two).
        nodes_id        : list
            List of all node identifier.

        Notes
        -----
        max_cliques : list[frozenset]
            List of frozenset containing node id for each maximal clique.
        """
        super(MaxCliquesPercolation, self).__init__(graph, edges_weight, nodes_id)

    def init_maxclique_percolation(self):
        """Initialization of maxial clique percolation method.

        The first step is to build temporary graph (percolation graph). Then the procedure finds
        all maximal cliques in the graph.
        """
        super(MaxCliquesPercolation, self)._build_temp_graph()
        maxcliques = self._find_maxcliques()
        self.cliques = maxcliques

    def get_maxcliques_percolation(self, k):
        """The main method to find clusters based on maximal clique percolation.

        This method looks for percolation between cliques.
        It then remove any edges that connecting two vertices but has different clusters.

        Returns
        -------
        k           : int
            Number of percolation or intersection between an individual clique.
        clusters    : dict[frozenset]
            List of frozenset containing node identifier (node id in integer).
        """
        super(MaxCliquesPercolation, self)._get_percolation_graph(self.cliques, k)
        super(MaxCliquesPercolation, self)._remove_outcluster()
        clusters = super(MaxCliquesPercolation, self)._get_clusters()

        return clusters

    def _find_maxcliques(self):
        """Find maximal cliques using `find_clique` function from NetworkX.

        Returns
        -------
        maxcliques  : list[frozenset]
            List of frozenset containing node id for each maximal clique.
        """
        maxcliques = list(frozenset(c) for c in nx.find_cliques(self.graph))
        self.cliques = maxcliques
        return maxcliques

class MaxCliquesPercolationWeighted(MaxCliquesPercolation):
    """This is a class for maximal clique percolation with edge weight [Liu2009]_.

    Edge weight is evaluated using intensity threshold or the geometric mean
    for all edge weights in a maximal clique [Studiawan2016c]_. We then remove the overlapping nodes
    where a node only follow the weighter neighboring cluster [Studiawan2016c]_.

    References
    ----------
    .. [Liu2009]        Guimei Liu, Limsoon Wong, and Hon Nian Chua. Complex discovery from
                        weighted PPI networks. Bioinformatics, 25(15):1891-1897, 2009.
    .. [Studiawan2016c] H. Studiawan, C. Payne, F. Sohel, SSH log clustering using weighted
                        maximal clique percolation (to be submitted).
    """
    def __init__(self, graph, edges_weight, nodes_id):
        """This is the constructor of class MaxCliquePercolation Weighted.

        The parameters are the same with its parent class but we add a threshold
        for the intensity for maximal clique found.

        Parameters
        ----------
        graph           : graph
            Graph to be clustered.
        edges_weight    : list[tuple]
            List of tuple containing (node1, node2, cosine similarity between these two).
        nodes_id        : list
            List of all node identifier.
        """
        super(MaxCliquesPercolationWeighted, self).__init__(graph, edges_weight, nodes_id)
        self.percolation_dict = {}

    def get_maxcliques_percolation_weighted(self, k, threshold):
        """This is the main method for maximal clique percolation for edge-weighted graph.

        Parameters
        ----------
        k           : int
            Number of percolation or intersection between an individual clique.
        threshold   : float
            Threshold for intensity of maximal clique.

        Returns
        -------
        clusters    : dict[int, frozenset]
            List of frozenset containing node identifier (node id in integer).

        Notes
        -----
        weighted_maxcliques : list[frozenset]
            List of frozenset containing node identifier for each weighted maximal clique.
        """
        # get weighted maximal cliques and get percolation
        weighted_maxcliques = ClusterUtility.get_weighted_cliques(self.graph, self.cliques, threshold)
        clusters = {}
        if weighted_maxcliques:
            super(MaxCliquesPercolationWeighted, self)._get_percolation_graph(weighted_maxcliques, k)
            self.__set_percolation_dict(self.clique_percolation)

            # remove overlapping nodes
            percolations = self.clique_percolation.values()
            for p1, p2 in combinations(percolations, 2):
                intersections = p1.intersection(p2)
                if intersections:
                    for node in intersections:
                        node_neighbors = self.graph.neighbors(node)
                        p1_neighbors, p2_neighbors = p1.intersection(node_neighbors), p2.intersection(node_neighbors)
                        p1_neighbors_weight = self.__get_neighbors_weight(node, p1_neighbors)
                        p2_neighbors_weight = self.__get_neighbors_weight(node, p2_neighbors)

                        # follow the neighboring cluster which has bigger sum of edge-weight
                        self.percolation_dict[node] = self.percolation_dict[list(p1_neighbors)[0]] \
                            if p1_neighbors_weight > p2_neighbors_weight else \
                            self.percolation_dict[list(p2_neighbors)[0]]

            self.__set_graph_cluster()
            clusters = self.__get_clusters()
        return clusters

    def __set_percolation_dict(self, percolations):
        """Set dictionary of node id and its index in percolations.

        Parameters
        ----------
        percolations    : dict[frozenset]
            Dictionary of frozenset containing nodes id for each maximal clique.
        """
        nodes = self.graph.nodes()
        percolations_merged = []
        for index, percolation in percolations.iteritems():
            for p in percolation:
                self.percolation_dict[p] = index

            # merge percolations list
            percolations_merged += percolation

        diff = set(nodes).difference(percolations_merged)
        other_cluster = len(percolations)
        if diff:
            for i in diff:
                self.percolation_dict[i] = other_cluster
                other_cluster += 1

    def __get_neighbors_weight(self, node, neighbors):
        """Get all weight of neighboring cluster.

        Parameters
        ----------
        node        : int
            Node identifier
        neighbors   : list[int]
            List of node identifier of intersection between two clusters

        Returns
        -------
        weight  : int
            Sum of all edge weight from specific neighboring cluster.
        """
        weight = 0
        for n in neighbors:
            weight += self.graph[node][n][0]['weight']

        return weight

    def __set_graph_cluster(self):
        """Set cluster id in the given graph based on percolation dictionary.

        Returns
        -------
        self.graph  : graph
            Graph with updated cluster identifier after cluster processing.
        """
        for node in self.graph.nodes_iter(data=True):
            self.graph.node[node[0]]['cluster'] = self.percolation_dict[node[0]]
        return self.graph

    def __get_clusters(self):
        """Get maximal clique percolation as clusters with incremental cluster id.

        Returns
        -------
        clusters    : dict[list]
            Dictionary of list containing node identifier for each cluster found.
        """
        cluster_ids = set(self.percolation_dict.values())
        clusters = {}
        cluster_idx = 0
        for ids in cluster_ids:
            cluster = []
            for node, cluster_id in self.percolation_dict.iteritems():
                if ids == cluster_id:
                    cluster.append(node)
            clusters[cluster_idx] = cluster
            cluster_idx += 1

        return clusters
<|endoftext|>


<commit_before>"""
Open Dynamics Engine-based pool physics simulator
"""
import logging
import numpy as np
import ode

from .table import PoolTable
from .physics import _J, PoolPhysics
from .sound import play_ball_ball_collision_sound

_logger = logging.getLogger(__name__)

INCH2METER = 0.0254

ZERO3 = np.zeros(3, dtype=np.float64)

def _create_ball(world, ball_mass, ball_radius, space=None):
    mass = ode.Mass()
    mass.setSphereTotal(ball_mass, ball_radius)
    body = ode.Body(world)
    body.setMass(mass)
    body.shape = "sphere"
    body.boxsize = (2*ball_radius, 2*ball_radius, 2*ball_radius)
    geom = ode.GeomSphere(space=space, radius=ball_radius)
    geom.setBody(body)
    return body, geom

def _create_cue(world, cue_mass, cue_radius, cue_length, space=None, kinematic=True):
    body = ode.Body(world)
    mass = ode.Mass()
    mass.setCylinderTotal(cue_mass, 3, cue_radius, cue_length)
    body.setMass(mass)
    body.shape = "cylinder"
    if kinematic:
        body.setKinematic()
    if space:
        geom = ode.GeomCylinder(space=space, radius=cue_radius, length=cue_length)
        geom.setBody(body)
        return body, geom
    return body

class ODEPoolPhysics(object):
    PhysicsEvent = PoolPhysics.PhysicsEvent
    StrikeBallEvent = PoolPhysics.StrikeBallEvent
    SlideToRollEvent = PoolPhysics.SlideToRollEvent
    RestEvent = PoolPhysics.RestEvent
    SlideToRestEvent = PoolPhysics.SlideToRestEvent
    RollToRestEvent = PoolPhysics.RollToRestEvent
    PositionBallEvent = PoolPhysics.PositionBallEvent
    SimpleBallCollisionEvent = PoolPhysics.SimpleBallCollisionEvent
    BallCollisionEvent = PoolPhysics.BallCollisionEvent

    STATIONARY = 0
    SLIDING    = 1
    ROLLING    = 2
    SPINNING   = 3

    def __init__(self,
                 num_balls=16,
                 ball_mass=0.17,
                 ball_radius=1.125*INCH2METER,
                 mu_r=0.016,
                 mu_sp=0.044,
                 mu_s=0.2,
                 mu_b=0.06,
                 c_b=4000.0,
                 E_Y_b=2.4e9,
                 g=9.81,
                 linear_damping=0.008,
                 angular_damping=0.0125,
                 initial_positions=None,
                 table=None,
                 **kwargs):
        self.PhysicsEvent.physics = self
        if table is None:
            table = PoolTable(**kwargs)
        self.table = table
        self.num_balls = num_balls
        self.ball_mass = ball_mass
        self.ball_radius = ball_radius
        self.mu_r = mu_r
        self.mu_sp = mu_sp
        self.mu_s = mu_s
        self.mu_b = mu_b
        self.c_b = c_b
        self.E_Y_b = E_Y_b
        self.g = g
        self.all_balls = list(range(num_balls))
        self.on_table = np.array(num_balls * [True])
        self.events = []
        self.ball_events = {i: [] for i in self.all_balls}
        self._I = 2.0/5 * ball_mass * ball_radius**2
        self.world = ode.World()
        self.world.setGravity((0.0, -g, 0.0))
        self.world.setLinearDamping(linear_damping)
        self.world.setAngularDamping(angular_damping)
        self.space = ode.SimpleSpace()
        self.t = 0.0
        self._t_last_strike = 0.0
        self.nsteps = 0
        self.ball_bodies = []
        self.ball_geoms = []
        for i in range(num_balls):
            body, geom = _create_ball(self.world, ball_mass, ball_radius, space=self.space)
            self.ball_bodies.append(body)
            self.ball_geoms.append(geom)
        # self.table_geom = ode.GeomBox(space=self.space, lengths=(self.table.width, self.table.height, self.table.length))
        self.table_geom = ode.GeomPlane(space=self.space, normal=(0.0, 1.0, 0.0),
                                        dist=self.table.height)
        def _setup_cushions():
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.headCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.headCushionGeom.indices.reshape(-1,3))
            self.head_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.leftHeadCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.leftHeadCushionGeom.indices.reshape(-1,3))
            self.left_head_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.rightHeadCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.rightHeadCushionGeom.indices.reshape(-1,3))
            self.right_head_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.footCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.footCushionGeom.indices.reshape(-1,3)[:,::-1])
            self.foot_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.leftFootCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.leftFootCushionGeom.indices.reshape(-1,3)[:,::-1])
            self.left_foot_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.rightFootCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.rightFootCushionGeom.indices.reshape(-1,3)[:,::-1])
            self.right_foot_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
        _setup_cushions()

        self._contactgroup = ode.JointGroup()
        self.events = []
        self.ball_events = {i: [] for i in self.all_balls}
        self._a = np.zeros((num_balls, 3, 3), dtype=np.float64)
        self._b = np.zeros((num_balls, 2, 3), dtype=np.float64)
        if initial_positions is not None:
            for body, position in zip(self.ball_bodies, initial_positions):
                body.setPosition(position)
            for geom, position in zip(self.ball_geoms, initial_positions):
                geom.setPosition(position)
            self._a[:,0] = initial_positions
        self._on_cue_ball_collide = None

    def set_cue_ball_collision_callback(self, cb):
        self._on_cue_ball_collide = cb

    def reset(self, ball_positions):
        self.on_table[:] = True
        for i, body in enumerate(self.ball_bodies):
            body.enable()
            body.setPosition(ball_positions[i])
            body.setLinearVel(ZERO3)
            body.setAngularVel(ZERO3)
        for i, geom in enumerate(self.ball_geoms):
            geom.setPosition(ball_positions[i])
        self._contactgroup.empty()
        self.events = []
        self.ball_events = {i: [] for i in self.all_balls}
        self._a[:] = 0
        self._a[:,0] = ball_positions

    def add_cue(self, cue):
        body, geom = _create_cue(self.world, cue.mass, cue.radius, cue.length,
                                 space=self.space, kinematic=True)
        self.cue_bodies = [body]
        self.cue_geoms = [geom]
        return body, geom

    def strike_ball(self, t, i, Q, V, cue_mass):
        if not self.on_table[i]:
            return
        body = self.ball_bodies[i]
        a, b, c = Q
        V = V.copy()
        V[1] = 0
        sin, cos = 0.0, 1.0
        M = cue_mass
        m, R = self.ball_mass, self.ball_radius
        norm_V = np.linalg.norm(V)
        F = 2.0 * m * norm_V / (1 + m/M + 5.0/(2*R**2) * (a**2 + (b*cos)**2 + (c*sin)**2 - 2*b*c*cos*sin))
        v = np.zeros(3, dtype=np.float64) # <-- post-impact ball velocity
        v[::2] = F / m * V[::2] / np.linalg.norm(V[::2])
        omega = np.zeros(3, dtype=np.float64) # <-- post-impact ball angular velocity
        I = self._I
        omega_i = F * (-c * sin + b * cos) / I
        omega_j = F * a * sin / I
        omega_k = -F * a * cos / I
        _j = -V[:] / norm_V
        _k = _J
        _i = np.cross(_j, _k)
        omega[:] = omega_i * _i + omega_j * _j + omega_k * _k
        body.setLinearVel(v)
        body.setAngularVel(omega)
        self._t_last_strike = t
        self._add_event(self.StrikeBallEvent(t, i, Q, V, cue_mass))
        self._add_event(self.RollToRestEvent(t + 12, i, self._a[i,0]))
        return 1

    def step(self, dt):
        self.space.collide((self.world, self._contactgroup), self._near_callback)
        self.world.step(dt)
        self._contactgroup.empty()
        self.t += dt
        self.nsteps += 1

    def eval_positions(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getPosition()
        return out

    def eval_quaternions(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getQuaternion()
        return out

    def eval_velocities(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getLinearVel()
        return out

    def eval_angular_velocities(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getAngularVel()
        return out

    def next_turn_time(self):
        return self._t_last_strike + 0.04

    def _add_event(self, event):
        self.events.append(event)
        self.ball_events[event.i].append(event)

    def _near_callback(self, args, geom1, geom2):
        world, contactgroup = args
        try:
            i = self.ball_geoms.index(geom1)
            if not self.on_table[i]:
                return
        except:
            pass
        try:
            j = self.ball_geoms.index(geom2)
            if not self.on_table[j]:
                return
        except:
            pass
        contacts = ode.collide(geom1, geom2)
        if contacts:
            body1, body2 = geom1.getBody(), geom2.getBody()
        for c in contacts:
            if isinstance(geom1, ode.GeomPlane) or isinstance(geom2, ode.GeomPlane):
                c.setBounce(0.24)
                c.setMu(0.16)
                c.setBounceVel(0.033)
                c.setSoftERP(0.4)
                c.setSoftCFM(1e4)
                c.setSlip1(0.05)
            elif isinstance(geom1, ode.GeomTriMesh) or isinstance(geom2, ode.GeomTriMesh):
                c.setBounce(0.86)
                c.setMu(0.16)
                c.setBounceVel(0.02)
                c.setSoftERP(0.4)
                c.setSoftCFM(1e2)
                c.setSlip1(0.04)
            elif isinstance(geom1, ode.GeomCylinder) or isinstance(geom2, ode.GeomCylinder):
                c.setBounce(0.69)
                c.setMu(0.25)
                c.setSoftERP(0.2)
                c.setSoftCFM(1e1)
                pos, normal, depth, g1, g2 = c.getContactGeomParams()
                v_n = np.array(normal).dot(np.array(body1.getPointVel(pos)) - np.array(body2.getPointVel(pos)))
                if self._on_cue_ball_collide:
                    self._on_cue_ball_collide(impact_speed=abs(v_n))
            else:
                c.setBounce(0.93)
                c.setMu(0.13)
                pos, normal, depth, g1, g2 = c.getContactGeomParams()
                v_n = abs(np.array(normal).dot(np.array(body1.getLinearVel()) - np.array(body2.getLinearVel())))
                vol = max(0.02, min(0.8, 0.45*v_n + 0.55*v_n**2))
                if vol > 0.02:
                    play_ball_ball_collision_sound(vol=vol)
            j = ode.ContactJoint(world, contactgroup, c)
            j.attach(body1, body2)
<commit_msg>fix ode phys<commit_after>"""
Open Dynamics Engine-based pool physics simulator
"""
import logging
_logger = logging.getLogger(__name__)
import numpy as np
try:
    import ode
except ImportError as err:
    _logger.error(err)
    raise err

from .table import PoolTable
from .physics import PoolPhysics
from .physics.events import BallRestEvent, CueStrikeEvent
from .sound import play_ball_ball_collision_sound

INCH2METER = 0.0254
ZERO3 = np.zeros(3, dtype=np.float64)
_J = np.array([0.0, 1.0, 0.0], dtype=np.float64)

def _create_ball(world, ball_mass, ball_radius, space=None):
    mass = ode.Mass()
    mass.setSphereTotal(ball_mass, ball_radius)
    body = ode.Body(world)
    body.setMass(mass)
    body.shape = "sphere"
    body.boxsize = (2*ball_radius, 2*ball_radius, 2*ball_radius)
    geom = ode.GeomSphere(space=space, radius=ball_radius)
    geom.setBody(body)
    return body, geom

def _create_cue(world, cue_mass, cue_radius, cue_length, space=None, kinematic=True):
    body = ode.Body(world)
    mass = ode.Mass()
    mass.setCylinderTotal(cue_mass, 3, cue_radius, cue_length)
    body.setMass(mass)
    body.shape = "cylinder"
    if kinematic:
        body.setKinematic()
    if space:
        geom = ode.GeomCylinder(space=space, radius=cue_radius, length=cue_length)
        geom.setBody(body)
        return body, geom
    return body

class ODEPoolPhysics(object):

    STATIONARY = 0
    SLIDING    = 1
    ROLLING    = 2
    SPINNING   = 3

    def __init__(self,
                 num_balls=16,
                 ball_mass=0.17,
                 ball_radius=1.125*INCH2METER,
                 mu_r=0.016,
                 mu_sp=0.044,
                 mu_s=0.2,
                 mu_b=0.06,
                 c_b=4000.0,
                 E_Y_b=2.4e9,
                 g=9.81,
                 linear_damping=0.008,
                 angular_damping=0.0125,
                 initial_positions=None,
                 table=None,
                 **kwargs):
        if table is None:
            table = PoolTable(**kwargs)
        self.table = table
        self.num_balls = num_balls
        self.ball_mass = ball_mass
        self.ball_radius = ball_radius
        self.mu_r = mu_r
        self.mu_sp = mu_sp
        self.mu_s = mu_s
        self.mu_b = mu_b
        self.c_b = c_b
        self.E_Y_b = E_Y_b
        self.g = g
        self.all_balls = list(range(num_balls))
        self.on_table = np.array(num_balls * [True])
        self.events = []
        self.ball_events = {i: [] for i in self.all_balls}
        self._I = 2.0/5 * ball_mass * ball_radius**2
        self.world = ode.World()
        self.world.setGravity((0.0, -g, 0.0))
        self.world.setLinearDamping(linear_damping)
        self.world.setAngularDamping(angular_damping)
        self.space = ode.SimpleSpace()
        self.t = 0.0
        self._t_last_strike = 0.0
        self.nsteps = 0
        self.ball_bodies = []
        self.ball_geoms = []
        for i in range(num_balls):
            body, geom = _create_ball(self.world, ball_mass, ball_radius, space=self.space)
            self.ball_bodies.append(body)
            self.ball_geoms.append(geom)
        # self.table_geom = ode.GeomBox(space=self.space, lengths=(self.table.width, self.table.height, self.table.length))
        self.table_geom = ode.GeomPlane(space=self.space, normal=(0.0, 1.0, 0.0),
                                        dist=self.table.height)
        def _setup_cushions():
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.headCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.headCushionGeom.indices.reshape(-1,3))
            self.head_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.leftHeadCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.leftHeadCushionGeom.indices.reshape(-1,3))
            self.left_head_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.rightHeadCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.rightHeadCushionGeom.indices.reshape(-1,3))
            self.right_head_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.footCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.footCushionGeom.indices.reshape(-1,3)[:,::-1])
            self.foot_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.leftFootCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.leftFootCushionGeom.indices.reshape(-1,3)[:,::-1])
            self.left_foot_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
            tri_mesh_data = ode.TriMeshData()
            tri_mesh_data.build(self.table.rightFootCushionGeom.attributes['vertices'].reshape(-1,3).tolist(), self.table.rightFootCushionGeom.indices.reshape(-1,3)[:,::-1])
            self.right_foot_cushion_geom = ode.GeomTriMesh(tri_mesh_data, space=self.space)
        _setup_cushions()

        self._contactgroup = ode.JointGroup()
        self.events = []
        self.ball_events = {i: [] for i in self.all_balls}
        self._a = np.zeros((num_balls, 3, 3), dtype=np.float64)
        self._b = np.zeros((num_balls, 2, 3), dtype=np.float64)
        if initial_positions is not None:
            for body, position in zip(self.ball_bodies, initial_positions):
                body.setPosition(position)
            for geom, position in zip(self.ball_geoms, initial_positions):
                geom.setPosition(position)
            self._a[:,0] = initial_positions
        self._on_cue_ball_collide = None

    def set_cue_ball_collision_callback(self, cb):
        self._on_cue_ball_collide = cb

    def reset(self, ball_positions):
        self.on_table[:] = True
        for i, body in enumerate(self.ball_bodies):
            body.enable()
            body.setPosition(ball_positions[i])
            body.setLinearVel(ZERO3)
            body.setAngularVel(ZERO3)
        for i, geom in enumerate(self.ball_geoms):
            geom.setPosition(ball_positions[i])
        self._contactgroup.empty()
        self.events = []
        self.ball_events = {i: [] for i in self.all_balls}
        self._a[:] = 0
        self._a[:,0] = ball_positions

    def add_cue(self, cue):
        body, geom = _create_cue(self.world, cue.mass, cue.radius, cue.length,
                                 space=self.space, kinematic=True)
        self.cue_bodies = [body]
        self.cue_geoms = [geom]
        return body, geom

    def strike_ball(self, t, i, Q, V, cue_mass):
        if not self.on_table[i]:
            return
        body = self.ball_bodies[i]
        a, b, c = Q
        V = V.copy()
        V[1] = 0
        sin, cos = 0.0, 1.0
        M = cue_mass
        m, R = self.ball_mass, self.ball_radius
        norm_V = np.linalg.norm(V)
        F = 2.0 * m * norm_V / (1 + m/M + 5.0/(2*R**2) * (a**2 + (b*cos)**2 + (c*sin)**2 - 2*b*c*cos*sin))
        v = np.zeros(3, dtype=np.float64) # <-- post-impact ball velocity
        v[::2] = F / m * V[::2] / np.linalg.norm(V[::2])
        omega = np.zeros(3, dtype=np.float64) # <-- post-impact ball angular velocity
        I = self._I
        omega_i = F * (-c * sin + b * cos) / I
        omega_j = F * a * sin / I
        omega_k = -F * a * cos / I
        _j = -V[:] / norm_V
        _k = _J
        _i = np.cross(_j, _k)
        omega[:] = omega_i * _i + omega_j * _j + omega_k * _k
        body.setLinearVel(v)
        body.setAngularVel(omega)
        self._t_last_strike = t
        self._add_event(CueStrikeEvent(t, i, self._a[i,0], Q + self._a[i,0], V, cue_mass))
        self._add_event(BallRestEvent(t + 12, i, self._a[i,0]))
        return 1

    def step(self, dt):
        self.space.collide((self.world, self._contactgroup), self._near_callback)
        self.world.step(dt)
        self._contactgroup.empty()
        self.t += dt
        self.nsteps += 1

    def eval_positions(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getPosition()
        return out

    def eval_quaternions(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getQuaternion()
        return out

    def eval_velocities(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getLinearVel()
        return out

    def eval_angular_velocities(self, t, balls=None, out=None):
        if balls is None:
            balls = self.all_balls
        if out is None:
            out = np.empty((len(balls), 3), dtype=np.float64)
        for ii, i in enumerate(balls):
            out[ii] = self.ball_bodies[i].getAngularVel()
        return out

    def next_turn_time(self):
        return self._t_last_strike + 0.04

    def _add_event(self, event):
        self.events.append(event)
        self.ball_events[event.i].append(event)

    def _near_callback(self, args, geom1, geom2):
        world, contactgroup = args
        try:
            i = self.ball_geoms.index(geom1)
            if not self.on_table[i]:
                return
        except:
            pass
        try:
            j = self.ball_geoms.index(geom2)
            if not self.on_table[j]:
                return
        except:
            pass
        contacts = ode.collide(geom1, geom2)
        if contacts:
            body1, body2 = geom1.getBody(), geom2.getBody()
        for c in contacts:
            if isinstance(geom1, ode.GeomPlane) or isinstance(geom2, ode.GeomPlane):
                c.setBounce(0.24)
                c.setMu(0.16)
                c.setBounceVel(0.033)
                c.setSoftERP(0.4)
                c.setSoftCFM(1e4)
                c.setSlip1(0.05)
            elif isinstance(geom1, ode.GeomTriMesh) or isinstance(geom2, ode.GeomTriMesh):
                c.setBounce(0.86)
                c.setMu(0.16)
                c.setBounceVel(0.02)
                c.setSoftERP(0.4)
                c.setSoftCFM(1e2)
                c.setSlip1(0.04)
            elif isinstance(geom1, ode.GeomCylinder) or isinstance(geom2, ode.GeomCylinder):
                c.setBounce(0.69)
                c.setMu(0.25)
                c.setSoftERP(0.2)
                c.setSoftCFM(1e1)
                pos, normal, depth, g1, g2 = c.getContactGeomParams()
                v_n = np.array(normal).dot(np.array(body1.getPointVel(pos)) - np.array(body2.getPointVel(pos)))
                if self._on_cue_ball_collide:
                    self._on_cue_ball_collide(impact_speed=abs(v_n))
            else:
                c.setBounce(0.93)
                c.setMu(0.13)
                pos, normal, depth, g1, g2 = c.getContactGeomParams()
                v_n = abs(np.array(normal).dot(np.array(body1.getLinearVel()) - np.array(body2.getLinearVel())))
                vol = max(0.02, min(0.8, 0.45*v_n + 0.55*v_n**2))
                if vol > 0.02:
                    play_ball_ball_collision_sound(vol=vol)
            j = ode.ContactJoint(world, contactgroup, c)
            j.attach(body1, body2)
<|endoftext|>


<commit_before>'''Classes to implement responses an EvilMinion returns to its master'''

import logging

import tornado.gen

from dump_reader import DumpReader

log = logging.getLogger(__name__)

class Reactor(object):
    '''Reacts to PUB events by responding with REQs from a dump'''
    def __init__(self, tok, channel, dump_path, opts):
        self.tok = tok
        self.channel = channel
        self.minion_id = opts['id']
        self.machine_id = opts['machine_id']
        self.master = opts['master']

        self.dump_reader = DumpReader(dump_path, {
            'tok': self.tok,
            'id': self.minion_id,
            'machine_id': self.machine_id,
            'master': self.master,
            'master_ip': self.master,
        })

    @tornado.gen.coroutine
    def start(self):
        '''Dispatches REQs that fire off automatically when the minion starts'''
        self.dispatch({'load': {'fun': None, 'arg': None, 'tgt': self.minion_id, 'load': None, 'jid': None}})

    @tornado.gen.coroutine
    def dispatch(self, load):
        '''Finds appropriate reactions to a PUB message and dispatches them'''
        load = load['load']
        fun = load['fun']
        tgt = load['tgt']

        if tgt != '*' and self.minion_id not in tgt:
            log.debug("Ignoring %s call that targets %s, not me (%s)", fun, tgt, self.minion_id)
            return

        reactions = self.dump_reader.get_reactions_to(load)
        ret = yield self.react(load, reactions)

    @tornado.gen.coroutine
    def react(self, load, reactions):
        '''Dispatches reactions in response to some load'''
        for reaction in reactions:
            request = reaction['load']
            if request['cmd'] == '_return' and request.get('fun') == load.get('fun'):
                request['jid'] = load['jid']
                if load.has_key('metadata'):
                    request['metadata']['suma-action-id'] = load['metadata'].get('suma-action-id')
            yield self.channel.send(request, timeout=60)
<commit_msg>Reactor: decrease verbosity<commit_after>'''Classes to implement responses an EvilMinion returns to its master'''

import logging

import tornado.gen

from dump_reader import DumpReader

log = logging.getLogger(__name__)

class Reactor(object):
    '''Reacts to PUB events by responding with REQs from a dump'''
    def __init__(self, tok, channel, dump_path, opts):
        self.tok = tok
        self.channel = channel
        self.minion_id = opts['id']
        self.machine_id = opts['machine_id']
        self.master = opts['master']

        self.dump_reader = DumpReader(dump_path, {
            'tok': self.tok,
            'id': self.minion_id,
            'machine_id': self.machine_id,
            'master': self.master,
            'master_ip': self.master,
        })

    @tornado.gen.coroutine
    def start(self):
        '''Dispatches REQs that fire off automatically when the minion starts'''
        self.dispatch({'load': {'fun': None, 'arg': None, 'tgt': self.minion_id, 'load': None, 'jid': None}})

    @tornado.gen.coroutine
    def dispatch(self, load):
        '''Finds appropriate reactions to a PUB message and dispatches them'''
        load = load['load']
        fun = load['fun']
        tgt = load['tgt']

        if tgt != '*' and self.minion_id not in tgt:
            log.trace("Ignoring %s call that targets %s, not me (%s)", fun, tgt, self.minion_id)
            return

        reactions = self.dump_reader.get_reactions_to(load)
        ret = yield self.react(load, reactions)

    @tornado.gen.coroutine
    def react(self, load, reactions):
        '''Dispatches reactions in response to some load'''
        for reaction in reactions:
            request = reaction['load']
            if request['cmd'] == '_return' and request.get('fun') == load.get('fun'):
                request['jid'] = load['jid']
                if load.has_key('metadata'):
                    request['metadata']['suma-action-id'] = load['metadata'].get('suma-action-id')
            yield self.channel.send(request, timeout=60)
<|endoftext|>