<commit_before># Under MIT licence, see LICENCE.txt

import math as m
import time
from typing import List, Union

import numpy as np

from Util.constant import ROBOT_CENTER_TO_KICKER, BALL_RADIUS, KickForce
from Util import Pose, Position
from Util.ai_command import CmdBuilder, Idle
from Util.geometry import compare_angle, normalize
from ai.Algorithm.evaluation_module import best_passing_option
from ai.GameDomainObjects import Player
from ai.STA.Tactic.tactic import Tactic
from ai.STA.Tactic.tactic_constants import Flags
from ai.states.game_state import GameState

VALIDATE_KICK_DELAY = 0.5
TARGET_ASSIGNATION_DELAY = 1

GO_BEHIND_SPACING = 200
GRAB_BALL_SPACING = 100
APPROACH_SPEED = 100
KICK_DISTANCE = 130
KICK_SUCCEED_THRESHOLD = 600
COMMAND_DELAY = 0.5

# noinspection PyArgumentList,PyUnresolvedReferences,PyUnresolvedReferences
class GoKick(Tactic):
    def __init__(self, game_state: GameState, player: Player,
                 target: Pose=Pose(),
                 args: List[str]=None,
                 kick_force: KickForce=KickForce.MEDIUM,
                 auto_update_target=False):

        super().__init__(game_state, player, target, args)
        self.current_state = self.kick_charge
        self.next_state = self.kick_charge
        self.cmd_last_time = time.time()
        self.kick_last_time = time.time()
        self.auto_update_target = auto_update_target
        self.target_assignation_last_time = 0
        self.target = target
        if self.auto_update_target:
            self._find_best_passing_option()
        self.kick_force = kick_force
        self.ball_spacing = GRAB_BALL_SPACING
        self.tries_flag = 0
        self.grab_ball_tries = 0

    def kick_charge(self):
        if time.time() - self.cmd_last_time > COMMAND_DELAY:
            self.next_state = self.go_behind_ball
            self.cmd_last_time = time.time()

        return CmdBuilder().addChargeKicker().build()

    def go_behind_ball(self):
        self.ball_spacing = GRAB_BALL_SPACING
        self.status_flag = Flags.WIP
        orientation = (self.target.position - self.player.pose.position).angle
        ball_speed = self.game_state.ball.velocity.norm
        ball_speed_modifier = (ball_speed/1000 + 1)

        distance_behind = self.get_destination_behind_ball(GRAB_BALL_SPACING * 3 * ball_speed_modifier)

        if (self.player.pose.position - distance_behind).norm < 50 \
                and compare_angle(self.player.pose.orientation, orientation, abs_tol=0.1):
            self.next_state = self.grab_ball
        else:
            self.next_state = self.go_behind_ball
            if self.auto_update_target:
                self._find_best_passing_option()
        # ball_collision = self.tries_flag == 0
        return CmdBuilder().addMoveTo(Pose(distance_behind, orientation),
                                      cruise_speed=2,
                                      end_speed=0,
                                      ball_collision=True).build()

    def grab_ball(self):
        if self._get_distance_from_ball() < (KICK_DISTANCE + self.grab_ball_tries * 10):
            self.next_state = self.kick

        orientation = (self.target.position - self.player.pose.position).angle
        distance_behind = self.get_destination_behind_ball(GRAB_BALL_SPACING)
        return CmdBuilder().addMoveTo(Pose(distance_behind, orientation),
                                      cruise_speed=1,
                                      ball_collision=False).addChargeKicker().build()

    def kick(self):
        self.ball_spacing = GRAB_BALL_SPACING
        self.next_state = self.validate_kick
        self.tries_flag += 1

        player_to_target = (self.target.position - self.player.pose.position)
        behind_ball = self.game_state.ball_position - normalize(player_to_target) * (BALL_RADIUS + ROBOT_CENTER_TO_KICKER)
        orientation = player_to_target.angle

        return CmdBuilder().addMoveTo(Pose(behind_ball, orientation)).addKick(self.kick_force).build()

    def validate_kick(self):
        if self.game_state.ball_velocity.norm > 1000 or self._get_distance_from_ball() > KICK_SUCCEED_THRESHOLD:
            self.next_state = self.halt
        elif self.kick_last_time - time.time() < VALIDATE_KICK_DELAY:
            self.next_state = self.kick
        else:
            self.status_flag = Flags.INIT
            self.next_state = self.go_behind_ball

        return CmdBuilder().build()

    def halt(self):
        if self.status_flag == Flags.INIT:
            self.next_state = self.kick_charge
        else:
            self.status_flag = Flags.SUCCESS
        return Idle

    def _get_distance_from_ball(self):
        return (self.player.pose.position - self.game_state.ball_position).norm

    def _is_player_towards_ball_and_target(self, abs_tol=m.pi/30):
        ball_position = self.game_state.ball_position
        target_to_ball = ball_position - self.target.position
        ball_to_player = self.player.pose.position - ball_position
        return compare_angle(target_to_ball.angle, ball_to_player.angle, abs_tol=abs_tol)

    def _find_best_passing_option(self):
        assignation_delay = (time.time() - self.target_assignation_last_time)

        if assignation_delay > TARGET_ASSIGNATION_DELAY:
            tentative_target_id = best_passing_option(self.player)
            if tentative_target_id is None:
                self.target = Pose.from_values(GameState().const["FIELD_THEIR_GOAL_X_EXTERNAL"], 0, 0)
            else:
                self.target = Pose(GameState().get_player_position(tentative_target_id))

            self.target_assignation_last_time = time.time()

    def get_destination_behind_ball(self, ball_spacing) -> Position:
        """
            Calcule le point situé à  x pixels derrière la position 1 par rapport à la position 2
            :return: Un tuple (Pose, kick) où Pose est la destination du joueur et kick est nul (on ne botte pas)
            """
        delta_x = self.target.position.x - self.game_state.ball_position.x
        delta_y = self.target.position.y - self.game_state.ball_position.y
        theta = np.math.atan2(delta_y, delta_x)

        x = self.game_state.ball_position.x - ball_spacing * np.math.cos(theta)
        y = self.game_state.ball_position.y - ball_spacing * np.math.sin(theta)

        player_x = self.player.pose.position.x
        player_y = self.player.pose.position.y

        if np.sqrt((player_x - x) ** 2 + (player_y - y) ** 2) < 50:
            x -= np.math.cos(theta) * 2
            y -= np.math.sin(theta) * 2
        destination_position = Position(x, y)

        return destination_position
<commit_msg>grab ball directly if possible<commit_after># Under MIT licence, see LICENCE.txt

import math as m
import time
from typing import List, Union

import numpy as np

from Util.constant import ROBOT_CENTER_TO_KICKER, BALL_RADIUS, KickForce
from Util import Pose, Position
from Util.ai_command import CmdBuilder, Idle
from Util.geometry import compare_angle, normalize
from ai.Algorithm.evaluation_module import best_passing_option
from ai.GameDomainObjects import Player
from ai.STA.Tactic.tactic import Tactic
from ai.STA.Tactic.tactic_constants import Flags
from ai.states.game_state import GameState

VALIDATE_KICK_DELAY = 0.5
TARGET_ASSIGNATION_DELAY = 1

GO_BEHIND_SPACING = 200
GRAB_BALL_SPACING = 100
APPROACH_SPEED = 100
KICK_DISTANCE = 130
KICK_SUCCEED_THRESHOLD = 600
COMMAND_DELAY = 0.5

# noinspection PyArgumentList,PyUnresolvedReferences,PyUnresolvedReferences
class GoKick(Tactic):
    def __init__(self, game_state: GameState, player: Player,
                 target: Pose=Pose(),
                 args: List[str]=None,
                 kick_force: KickForce=KickForce.MEDIUM,
                 auto_update_target=False):

        super().__init__(game_state, player, target, args)
        self.current_state = self.kick_charge
        self.next_state = self.kick_charge
        self.cmd_last_time = time.time()
        self.kick_last_time = time.time()
        self.auto_update_target = auto_update_target
        self.target_assignation_last_time = 0
        self.target = target
        if self.auto_update_target:
            self._find_best_passing_option()
        self.kick_force = kick_force
        self.ball_spacing = GRAB_BALL_SPACING
        self.tries_flag = 0
        self.grab_ball_tries = 0

    def kick_charge(self):
        if time.time() - self.cmd_last_time > COMMAND_DELAY:
            if self._get_distance_from_ball() < (KICK_DISTANCE + self.grab_ball_tries * 10):
                self.next_state = self.grab_ball
            else:
                self.next_state = self.go_behind_ball
            self.cmd_last_time = time.time()

        return CmdBuilder().addChargeKicker().build()

    def go_behind_ball(self):
        self.ball_spacing = GRAB_BALL_SPACING
        self.status_flag = Flags.WIP
        orientation = (self.target.position - self.player.pose.position).angle
        ball_speed = self.game_state.ball.velocity.norm
        ball_speed_modifier = (ball_speed/1000 + 1)

        distance_behind = self.get_destination_behind_ball(GRAB_BALL_SPACING * 3 * ball_speed_modifier)

        if (self.player.pose.position - distance_behind).norm < 50 \
                and compare_angle(self.player.pose.orientation, orientation, abs_tol=0.1):
            self.next_state = self.grab_ball
        else:
            self.next_state = self.go_behind_ball
            if self.auto_update_target:
                self._find_best_passing_option()
        # ball_collision = self.tries_flag == 0
        return CmdBuilder().addMoveTo(Pose(distance_behind, orientation),
                                      cruise_speed=2,
                                      end_speed=0,
                                      ball_collision=True).build()

    def grab_ball(self):
        if self._get_distance_from_ball() < (KICK_DISTANCE + self.grab_ball_tries * 10):
            self.next_state = self.kick

        orientation = (self.target.position - self.player.pose.position).angle
        distance_behind = self.get_destination_behind_ball(GRAB_BALL_SPACING)
        return CmdBuilder().addMoveTo(Pose(distance_behind, orientation),
                                      cruise_speed=1,
                                      ball_collision=False).addChargeKicker().build()

    def kick(self):
        self.ball_spacing = GRAB_BALL_SPACING
        self.next_state = self.validate_kick
        self.tries_flag += 1

        player_to_target = (self.target.position - self.player.pose.position)
        behind_ball = self.game_state.ball_position - normalize(player_to_target) * (BALL_RADIUS + ROBOT_CENTER_TO_KICKER)
        orientation = player_to_target.angle

        return CmdBuilder().addMoveTo(Pose(behind_ball, orientation)).addKick(self.kick_force).build()

    def validate_kick(self):
        if self.game_state.ball_velocity.norm > 1000 or self._get_distance_from_ball() > KICK_SUCCEED_THRESHOLD:
            self.next_state = self.halt
        elif self.kick_last_time - time.time() < VALIDATE_KICK_DELAY:
            self.next_state = self.kick
        else:
            self.status_flag = Flags.INIT
            self.next_state = self.go_behind_ball

        return CmdBuilder().build()

    def halt(self):
        if self.status_flag == Flags.INIT:
            self.next_state = self.kick_charge
        else:
            self.status_flag = Flags.SUCCESS
        return Idle

    def _get_distance_from_ball(self):
        return (self.player.pose.position - self.game_state.ball_position).norm

    def _is_player_towards_ball_and_target(self, abs_tol=m.pi/30):
        ball_position = self.game_state.ball_position
        target_to_ball = ball_position - self.target.position
        ball_to_player = self.player.pose.position - ball_position
        return compare_angle(target_to_ball.angle, ball_to_player.angle, abs_tol=abs_tol)

    def _find_best_passing_option(self):
        assignation_delay = (time.time() - self.target_assignation_last_time)

        if assignation_delay > TARGET_ASSIGNATION_DELAY:
            tentative_target_id = best_passing_option(self.player)
            if tentative_target_id is None:
                self.target = Pose.from_values(GameState().const["FIELD_THEIR_GOAL_X_EXTERNAL"], 0, 0)
            else:
                self.target = Pose(GameState().get_player_position(tentative_target_id))

            self.target_assignation_last_time = time.time()

    def get_destination_behind_ball(self, ball_spacing) -> Position:
        """
            Calcule le point situé à  x pixels derrière la position 1 par rapport à la position 2
            :return: Un tuple (Pose, kick) où Pose est la destination du joueur et kick est nul (on ne botte pas)
            """
        delta_x = self.target.position.x - self.game_state.ball_position.x
        delta_y = self.target.position.y - self.game_state.ball_position.y
        theta = np.math.atan2(delta_y, delta_x)

        x = self.game_state.ball_position.x - ball_spacing * np.math.cos(theta)
        y = self.game_state.ball_position.y - ball_spacing * np.math.sin(theta)

        player_x = self.player.pose.position.x
        player_y = self.player.pose.position.y

        if np.sqrt((player_x - x) ** 2 + (player_y - y) ** 2) < 50:
            x -= np.math.cos(theta) * 2
            y -= np.math.sin(theta) * 2
        destination_position = Position(x, y)

        return destination_position
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
Take as input copy paste from chat and outputs a summary of the battle

Example usage:
    $ cat ~/Downloads/habitica_battle.txt | python battle_summary.py

Example outputs:

Battle Summary
==============

User               | Damage given | Damage taken | K/D Ratio
-------------------|--------------|--------------|----------
Nyne               | 29.2         | 3.0          | 9.73333333333
Sploshy            | 21.3         | 5.9          | 3.61016949153
Deena Bardsley     | 40.3         | 8.2          | 4.91463414634
kungfujam          | 64.8         | 2.2          | 29.4545454545
onemorego          | 102.4        | 2.6          | 39.3846153846
Jozef Mokry        | 7.5          | 3.0          | 2.5

Achievements
-------------
*Best player*:      onemorego (39.4 K/D Ratio)
*Damp squib*:       Jozef Mokry (2.5 K/D Ratio)
*The Warrior*:      onemorego (79.8 HP in one attack)
*Friendly Fire*:    Deena Bardsley (-6.7 HP taken from group in one day)
"""
import sys
import re
import operator

# Regex examples
# $name casts $spell for the party. $posneg$nrdays days ago$likes
# Deena Bardsley attacks Vice's Shade for 13.5 damage, Vice's Shade attacks
# party for 0.0 damage. -2 days

# attack = re.compile(r"""
# ^([a-zA-z ]+) attacks\s
# ([a-zA-z' ]+) for\s
# ([0-9\.]+) damage,\s
# [a-zA-z' ]+ attacks party for\s
# ([0-9\.]+) damage
# """, re.VERBOSE)
attack = re.compile("^([a-zA-z0-9 ]+) attacks ([a-zA-z' ]+) for ([0-9\.]+) "
                    "damage, [a-zA-z' ]+ attacks party for ([0-9\.]+) damage")

dmgDict = {}
for line in sys.stdin:
    if attack.match(line):
        userName, bossName, damageGiven, damageTaken = \
            attack.search(line).groups()
        if userName in dmgDict.keys():
            dmgDict[userName]['damageGiven'] += float(damageGiven)
            dmgDict[userName]['damageTaken'] += float(damageTaken)
            dg = dmgDict[userName]['damageGiven']
            dt = dmgDict[userName]['damageTaken']
            dmgDict[userName]['kd'] = \
                dg / max(dt, 1)
            dmgDict[userName]['nrAttacks'] += 1
        else:
            dmgDict[userName] = {
                'bossName': bossName,
                'damageGiven': float(damageGiven),
                'damageTaken': float(damageTaken),
                'kd': float(damageGiven)/max(float(damageTaken), 1),
                'maxAttack': float(damageGiven),
                'maxFU': float(damageTaken),
                'nrAttacks': 1
            }
        if float(damageGiven) > dmgDict[userName]['maxAttack']:
            dmgDict[userName]['maxAttack'] = float(damageGiven)
        if float(damageTaken) > dmgDict[userName]['maxFU']:
            dmgDict[userName]['maxFU'] = float(damageTaken)

maxAttacks = {key: value['maxAttack'] for key, value in dmgDict.iteritems()}
maxMaxAttack = max(maxAttacks.iteritems(), key=operator.itemgetter(1))
minMaxAttack = min(maxAttacks.iteritems(), key=operator.itemgetter(1))
attacks = {key: value['damageGiven'] for key, value in dmgDict.iteritems()}
minTotAttack = min(attacks.iteritems(), key=operator.itemgetter(1))
maxFUs = {key: value['maxFU'] for key, value in dmgDict.iteritems()}
maxMaxFU = max(maxFUs.iteritems(), key=operator.itemgetter(1))
FUs = {key: value['damageTaken'] for key, value in dmgDict.iteritems()}
minTotFU = min(FUs.iteritems(), key=operator.itemgetter(1))
maxTotFU = max(FUs.iteritems(), key=operator.itemgetter(1))
kds = maxFUs = {key: value['kd'] for key, value in dmgDict.iteritems()}
maxkd = max(kds.iteritems(), key=operator.itemgetter(1))
minkd = min(kds.iteritems(), key=operator.itemgetter(1))
nrAttacks = {key: value['nrAttacks'] for key, value in dmgDict.iteritems()}
minNrAttacks = min(nrAttacks.iteritems(), key=operator.itemgetter(1))
maxNrAttacks = max(nrAttacks.iteritems(), key=operator.itemgetter(1))

pad = 15
print "Battle Summary\n" + "="*14 + "\n"
print "User" + pad*" " + "| Damage given | Damage taken | K/D Ratio"
print (4 + pad) * "-" + "|--------------|--------------|----------"
for key, value in dmgDict.iteritems():
    print key.ljust(4 + pad) + "| " + str(value["damageGiven"]).ljust(13)\
        + "| " + "{:.1f}".format(value["damageTaken"]).ljust(13) + "| "\
        + "{:.1f}".format(value["kd"]).ljust(9)

print "\nAchievements\n" + "-" * 13
print "*Best player*:  \t" + "%s (%0.1f K/D Ratio)  " % maxkd
print "*Bravery*:      \t" + "%s (%d attacks)  " % maxNrAttacks
print "*Coward*:       \t" + "%s (only %d attacks)  " % minNrAttacks
print "*Damp squib*:   \t" + "%s (%0.1f K/D Ratio)  " % minkd
print "*The Warrior*:  \t" + "%s (%0.1f HP in one attack)  " % maxMaxAttack
print "*The Weakling*: \t" + "%s (%0.1f HP in one attack)  " % minMaxAttack
print "*Useless*:      \t" + "%s (%0.1f HP total attack)  " % minTotAttack
print "*Friendly Fire*:\t" + "%s (-%0.1f HP taken from group in one day)  "\
    % maxMaxFU
print "*Liability*:    \t" + "%s (-%0.1f HP total taken from group)  "\
    % maxTotFU
print "*Safe bet*:     \t" + "%s (-%0.1f only HP total taken from group)  "\
    % minTotFU
<commit_msg>Updated example output to include new achievements<commit_after>#!/usr/bin/env python
"""
Take as input copy paste from chat and outputs a summary of the battle

Example usage:
    $ cat ~/Downloads/habitica_battle.txt | python battle_summary.py

Example outputs:

Battle Summary
==============

User               | Damage given | Damage taken | K/D Ratio
-------------------|--------------|--------------|----------
Nyne               | 40.2         | 4.2          | 9.6
Deena Bardsley     | 40.3         | 8.2          | 4.9
s0min              | 17.2         | 0.0          | 17.2
Sploshy            | 28.6         | 7.4          | 3.9
onemorego          | 127.0        | 2.6          | 48.8
kungfujam          | 64.8         | 2.2          | 29.5
Jozef Mokry        | 10.1         | 3.0          | 3.4

Achievements
-------------
*Best player*:      onemorego (48.8 K/D Ratio)
*Bravery*:          Sploshy (4 attacks)
*Coward*:           kungfujam (only 2 attacks)
*Damp squib*:       Jozef Mokry (3.4 K/D Ratio)
*The Warrior*:      onemorego (79.8 HP in one attack)
*The Weakling*:     Jozef Mokry (3.8 HP in one attack)
*Useless*:          Jozef Mokry (10.1 HP total attack)
*Friendly Fire*:    Deena Bardsley (-6.7 HP taken from group in one day)
*Liability*:        Deena Bardsley (-8.2 HP total taken from group)
*Safe bet*:         s0min (-0.0 only HP total taken from group)
"""
import sys
import re
import operator

# Regex examples
# $name casts $spell for the party. $posneg$nrdays days ago$likes
# Deena Bardsley attacks Vice's Shade for 13.5 damage, Vice's Shade attacks
# party for 0.0 damage. -2 days

# attack = re.compile(r"""
# ^([a-zA-z ]+) attacks\s
# ([a-zA-z' ]+) for\s
# ([0-9\.]+) damage,\s
# [a-zA-z' ]+ attacks party for\s
# ([0-9\.]+) damage
# """, re.VERBOSE)
attack = re.compile("^([a-zA-z0-9 ]+) attacks ([a-zA-z' ]+) for ([0-9\.]+) "
                    "damage, [a-zA-z' ]+ attacks party for ([0-9\.]+) damage")

dmgDict = {}
for line in sys.stdin:
    if attack.match(line):
        userName, bossName, damageGiven, damageTaken = \
            attack.search(line).groups()
        if userName in dmgDict.keys():
            dmgDict[userName]['damageGiven'] += float(damageGiven)
            dmgDict[userName]['damageTaken'] += float(damageTaken)
            dg = dmgDict[userName]['damageGiven']
            dt = dmgDict[userName]['damageTaken']
            dmgDict[userName]['kd'] = \
                dg / max(dt, 1)
            dmgDict[userName]['nrAttacks'] += 1
        else:
            dmgDict[userName] = {
                'bossName': bossName,
                'damageGiven': float(damageGiven),
                'damageTaken': float(damageTaken),
                'kd': float(damageGiven)/max(float(damageTaken), 1),
                'maxAttack': float(damageGiven),
                'maxFU': float(damageTaken),
                'nrAttacks': 1
            }
        if float(damageGiven) > dmgDict[userName]['maxAttack']:
            dmgDict[userName]['maxAttack'] = float(damageGiven)
        if float(damageTaken) > dmgDict[userName]['maxFU']:
            dmgDict[userName]['maxFU'] = float(damageTaken)

maxAttacks = {key: value['maxAttack'] for key, value in dmgDict.iteritems()}
maxMaxAttack = max(maxAttacks.iteritems(), key=operator.itemgetter(1))
minMaxAttack = min(maxAttacks.iteritems(), key=operator.itemgetter(1))
attacks = {key: value['damageGiven'] for key, value in dmgDict.iteritems()}
minTotAttack = min(attacks.iteritems(), key=operator.itemgetter(1))
maxFUs = {key: value['maxFU'] for key, value in dmgDict.iteritems()}
maxMaxFU = max(maxFUs.iteritems(), key=operator.itemgetter(1))
FUs = {key: value['damageTaken'] for key, value in dmgDict.iteritems()}
minTotFU = min(FUs.iteritems(), key=operator.itemgetter(1))
maxTotFU = max(FUs.iteritems(), key=operator.itemgetter(1))
kds = maxFUs = {key: value['kd'] for key, value in dmgDict.iteritems()}
maxkd = max(kds.iteritems(), key=operator.itemgetter(1))
minkd = min(kds.iteritems(), key=operator.itemgetter(1))
nrAttacks = {key: value['nrAttacks'] for key, value in dmgDict.iteritems()}
minNrAttacks = min(nrAttacks.iteritems(), key=operator.itemgetter(1))
maxNrAttacks = max(nrAttacks.iteritems(), key=operator.itemgetter(1))

pad = 15
print "Battle Summary\n" + "="*14 + "\n"
print "User" + pad*" " + "| Damage given | Damage taken | K/D Ratio"
print (4 + pad) * "-" + "|--------------|--------------|----------"
for key, value in dmgDict.iteritems():
    print key.ljust(4 + pad) + "| " + str(value["damageGiven"]).ljust(13)\
        + "| " + "{:.1f}".format(value["damageTaken"]).ljust(13) + "| "\
        + "{:.1f}".format(value["kd"]).ljust(9)

print "\nAchievements\n" + "-" * 13
print "*Best player*:  \t" + "%s (%0.1f K/D Ratio)  " % maxkd
print "*Bravery*:      \t" + "%s (%d attacks)  " % maxNrAttacks
print "*Coward*:       \t" + "%s (only %d attacks)  " % minNrAttacks
print "*Damp squib*:   \t" + "%s (%0.1f K/D Ratio)  " % minkd
print "*The Warrior*:  \t" + "%s (%0.1f HP in one attack)  " % maxMaxAttack
print "*The Weakling*: \t" + "%s (%0.1f HP in one attack)  " % minMaxAttack
print "*Useless*:      \t" + "%s (%0.1f HP total attack)  " % minTotAttack
print "*Friendly Fire*:\t" + "%s (-%0.1f HP taken from group in one day)  "\
    % maxMaxFU
print "*Liability*:    \t" + "%s (-%0.1f HP total taken from group)  "\
    % maxTotFU
print "*Safe bet*:     \t" + "%s (-%0.1f only HP total taken from group)  "\
    % minTotFU
<|endoftext|>


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

from datetime import datetime
from sqlalchemy import func
from flask.ext.sqlalchemy import DeclarativeMeta
from collections import namedtuple
from .. import store

def _random_name():
    import uuid
    return str(uuid.uuid4())

def make(db):

    def noarg(t):
        return lambda primary_key=False, unique=False, **kargs: db.Column(
            t,
            primary_key=primary_key,
            unique=unique
        )

    FIELD_DICT = {
        str: lambda primary_key=False, unique=False, length=None, **kargs: db.Column(
            db.Text if length is None else db.String(length),
            primary_key=primary_key,
            unique=unique
        ),
        int: noarg(db.Integer),
        float: noarg(db.Float),
        bool: noarg(db.Boolean),
        datetime: noarg(db.DateTime),
        object: noarg(db.PickleType),
        bytes: noarg(db.LargeBinary)
    }

    class StoreMeta(store.StoreMeta):

        def __new__(meta, name, bases, attr):
            fields = []
            for base in bases:
                if hasattr(base, 'FIELDS'):
                    fields = getattr(base, 'FIELDS')
                    break

            for field in fields:
                if type(field) is tuple:
                    if len(field) == 2:
                        k, t = field
                        kargs = {}
                    elif len(field) == 3:
                        k, t, kargs = field
                    else:
                        assert False, 'wrong field format: {}'.format(field)
                else:
                    k = field
                    t = str
                    kargs = {}

                attr[k] = FIELD_DICT[t](**kargs)

            return store.StoreMeta.__new__(meta, name, bases, attr)

    class ModelMeta(StoreMeta, type(db.Model)):
        pass

    class Meta(store.Meta, db.Model, metaclass=ModelMeta):
        pass

    class Image(store.Image, db.Model, metaclass=ModelMeta):
        pass

    class Site(store.Site, db.Model, metaclass=ModelMeta):
        pass

    class Repo(store.Repo):

        def connection(self):
            return Connection()

    class Connection(store.Connection):

        def __enter__(self, *args, **kargs):
            return self

        def __exit__(self, *args, **kargs):
            return

        def commit(self):
            db.session.commit()

        def add_or_update(self, o):
            if o.id is None:
                o.id = _random_name()
            o = db.session.merge(o)
            db.session.add(o)

        def put(self, o):
            return self.add_or_update(o)

        def get_images_order_bi_ctime(self, r):
            return Image.query.order_by(Image.ctime.desc())[r]

        def get_site_bi_id(self, id):
            return Site.query.filter_by(id=id).first()

        def latest_ctime_bi_site_id(self, id):
            return db.session.query(func.max(Image.ctime)).first()[0]

        def site_count(self):
            return db.session.query(func.count(Site.id)).first()[0]

        def image_count(self):
            return db.session.query(func.count(Image.id)).first()[0]

        def set_meta(self, id, value):
            self.put(Meta(id=id, value=value))

        def get_meta(self, id):
            return Meta.query.filter_by(id=id).first()

    Store = namedtuple('Store', ('Meta', 'Image', 'Site', 'Repo', 'Connection'))
    return Store(
        Meta=Meta,
        Image=Image,
        Site=Site,
        Repo=Repo,
        Connection=Connection
    )
<commit_msg>remove site concept in sqlalchemy store<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime
from sqlalchemy import func
from flask.ext.sqlalchemy import DeclarativeMeta
from collections import namedtuple
from .. import store

def _random_name():
    import uuid
    return str(uuid.uuid4())

def make(db):

    def noarg(t):
        return lambda primary_key=False, unique=False, **kargs: db.Column(
            t,
            primary_key=primary_key,
            unique=unique
        )

    FIELD_DICT = {
        str: lambda primary_key=False, unique=False, length=None, **kargs: db.Column(
            db.Text if length is None else db.String(length),
            primary_key=primary_key,
            unique=unique
        ),
        int: noarg(db.Integer),
        float: noarg(db.Float),
        bool: noarg(db.Boolean),
        datetime: noarg(db.DateTime),
        object: noarg(db.PickleType),
        bytes: noarg(db.LargeBinary)
    }

    class StoreMeta(store.StoreMeta):

        def __new__(meta, name, bases, attr):
            fields = []
            for base in bases:
                if hasattr(base, 'FIELDS'):
                    fields = getattr(base, 'FIELDS')
                    break

            for field in fields:
                if type(field) is tuple:
                    if len(field) == 2:
                        k, t = field
                        kargs = {}
                    elif len(field) == 3:
                        k, t, kargs = field
                    else:
                        assert False, 'wrong field format: {}'.format(field)
                else:
                    k = field
                    t = str
                    kargs = {}

                attr[k] = FIELD_DICT[t](**kargs)

            return store.StoreMeta.__new__(meta, name, bases, attr)

    class ModelMeta(StoreMeta, type(db.Model)):
        pass

    class Meta(store.Meta, db.Model, metaclass=ModelMeta):
        pass

    class Image(store.Image, db.Model, metaclass=ModelMeta):
        pass

    class Repo(store.Repo):

        def connection(self):
            return Connection()

    class Connection(store.Connection):

        def __enter__(self, *args, **kargs):
            return self

        def __exit__(self, *args, **kargs):
            return

        def commit(self):
            db.session.commit()

        def add_or_update(self, o):
            if o.id is None:
                o.id = _random_name()
            o = db.session.merge(o)
            db.session.add(o)

        def put(self, o):
            return self.add_or_update(o)

        def get_images_order_bi_ctime(self, r):
            return Image.query.order_by(Image.ctime.desc())[r]

        def latest_ctime_bi_site_id(self, id):
            return db.session.query(func.max(Image.ctime)).first()[0]

        def image_count(self):
            return db.session.query(func.count(Image.id)).first()[0]

        def set_meta(self, id, value):
            self.put(Meta(id=id, value=value))

        def get_meta(self, id):
            return Meta.query.filter_by(id=id).first()

    Store = namedtuple('Store', ('Meta', 'Image', 'Repo', 'Connection'))
    return Store(
        Meta=Meta,
        Image=Image,
        Repo=Repo,
        Connection=Connection
    )
<|endoftext|>


<commit_before>""" Normalization layers and wrappers

Norm layer definitions that support fast norm and consistent channel arg order (always first arg).

Hacked together by / Copyright 2022 Ross Wightman
"""

import torch
import torch.nn as nn
import torch.nn.functional as F

from .fast_norm import is_fast_norm, fast_group_norm, fast_layer_norm

class GroupNorm(nn.GroupNorm):
    def __init__(self, num_channels, num_groups=32, eps=1e-5, affine=True):
        # NOTE num_channels is swapped to first arg for consistency in swapping norm layers with BN
        super().__init__(num_groups, num_channels, eps=eps, affine=affine)
        self.fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x):
        if self.fast_norm:
            return fast_group_norm(x, self.num_groups, self.weight, self.bias, self.eps)
        else:
            return F.group_norm(x, self.num_groups, self.weight, self.bias, self.eps)

class GroupNorm1(nn.GroupNorm):
    """ Group Normalization with 1 group.
    Input: tensor in shape [B, C, *]
    """

    def __init__(self, num_channels, **kwargs):
        super().__init__(1, num_channels, **kwargs)
        self.fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        if self.fast_norm:
            return fast_group_norm(x, self.num_groups, self.weight, self.bias, self.eps)
        else:
            return F.group_norm(x, self.num_groups, self.weight, self.bias, self.eps)

class LayerNorm(nn.LayerNorm):
    """ LayerNorm w/ fast norm option
    """
    def __init__(self, num_channels, eps=1e-6, affine=True):
        super().__init__(num_channels, eps=eps, elementwise_affine=affine)
        self._fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        if self._fast_norm:
           x = fast_layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        else:
            x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        return x

class LayerNorm2d(nn.LayerNorm):
    """ LayerNorm for channels of '2D' spatial NCHW tensors """
    def __init__(self, num_channels, eps=1e-6, affine=True):
        super().__init__(num_channels, eps=eps, elementwise_affine=affine)
        self._fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = x.permute(0, 2, 3, 1)
        if self._fast_norm:
           x = fast_layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        else:
            x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        x = x.permute(0, 3, 1, 2)
        return x

def _is_contiguous(tensor: torch.Tensor) -> bool:
    # jit is oh so lovely :/
    if torch.jit.is_scripting():
        return tensor.is_contiguous()
    else:
        return tensor.is_contiguous(memory_format=torch.contiguous_format)

@torch.jit.script
def _layer_norm_cf(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, eps: float):
    s, u = torch.var_mean(x, dim=1, unbiased=False, keepdim=True)
    x = (x - u) * torch.rsqrt(s + eps)
    x = x * weight[:, None, None] + bias[:, None, None]
    return x

def _layer_norm_cf_sqm(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, eps: float):
    u = x.mean(dim=1, keepdim=True)
    s = ((x * x).mean(dim=1, keepdim=True) - (u * u)).clamp(0)
    x = (x - u) * torch.rsqrt(s + eps)
    x = x * weight.view(1, -1, 1, 1) + bias.view(1, -1, 1, 1)
    return x

class LayerNormExp2d(nn.LayerNorm):
    """ LayerNorm for channels_first tensors with 2d spatial dimensions (ie N, C, H, W).

    Experimental implementation w/ manual norm for tensors non-contiguous tensors.

    This improves throughput in some scenarios (tested on Ampere GPU), esp w/ channels_last
    layout. However, benefits are not always clear and can perform worse on other GPUs.
    """

    def __init__(self, num_channels, eps=1e-6):
        super().__init__(num_channels, eps=eps)

    def forward(self, x) -> torch.Tensor:
        if _is_contiguous(x):
            x = F.layer_norm(
                x.permute(0, 2, 3, 1), self.normalized_shape, self.weight, self.bias, self.eps).permute(0, 3, 1, 2)
        else:
            x = _layer_norm_cf(x, self.weight, self.bias, self.eps)
        return x
<commit_msg>Fix spacing misalignment for fast norm path in LayerNorm modules<commit_after>""" Normalization layers and wrappers

Norm layer definitions that support fast norm and consistent channel arg order (always first arg).

Hacked together by / Copyright 2022 Ross Wightman
"""

import torch
import torch.nn as nn
import torch.nn.functional as F

from .fast_norm import is_fast_norm, fast_group_norm, fast_layer_norm

class GroupNorm(nn.GroupNorm):
    def __init__(self, num_channels, num_groups=32, eps=1e-5, affine=True):
        # NOTE num_channels is swapped to first arg for consistency in swapping norm layers with BN
        super().__init__(num_groups, num_channels, eps=eps, affine=affine)
        self.fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x):
        if self.fast_norm:
            return fast_group_norm(x, self.num_groups, self.weight, self.bias, self.eps)
        else:
            return F.group_norm(x, self.num_groups, self.weight, self.bias, self.eps)

class GroupNorm1(nn.GroupNorm):
    """ Group Normalization with 1 group.
    Input: tensor in shape [B, C, *]
    """

    def __init__(self, num_channels, **kwargs):
        super().__init__(1, num_channels, **kwargs)
        self.fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        if self.fast_norm:
            return fast_group_norm(x, self.num_groups, self.weight, self.bias, self.eps)
        else:
            return F.group_norm(x, self.num_groups, self.weight, self.bias, self.eps)

class LayerNorm(nn.LayerNorm):
    """ LayerNorm w/ fast norm option
    """
    def __init__(self, num_channels, eps=1e-6, affine=True):
        super().__init__(num_channels, eps=eps, elementwise_affine=affine)
        self._fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        if self._fast_norm:
            x = fast_layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        else:
            x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        return x

class LayerNorm2d(nn.LayerNorm):
    """ LayerNorm for channels of '2D' spatial NCHW tensors """
    def __init__(self, num_channels, eps=1e-6, affine=True):
        super().__init__(num_channels, eps=eps, elementwise_affine=affine)
        self._fast_norm = is_fast_norm()  # can't script unless we have these flags here (no globals)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = x.permute(0, 2, 3, 1)
        if self._fast_norm:
            x = fast_layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        else:
            x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
        x = x.permute(0, 3, 1, 2)
        return x

def _is_contiguous(tensor: torch.Tensor) -> bool:
    # jit is oh so lovely :/
    if torch.jit.is_scripting():
        return tensor.is_contiguous()
    else:
        return tensor.is_contiguous(memory_format=torch.contiguous_format)

@torch.jit.script
def _layer_norm_cf(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, eps: float):
    s, u = torch.var_mean(x, dim=1, unbiased=False, keepdim=True)
    x = (x - u) * torch.rsqrt(s + eps)
    x = x * weight[:, None, None] + bias[:, None, None]
    return x

def _layer_norm_cf_sqm(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, eps: float):
    u = x.mean(dim=1, keepdim=True)
    s = ((x * x).mean(dim=1, keepdim=True) - (u * u)).clamp(0)
    x = (x - u) * torch.rsqrt(s + eps)
    x = x * weight.view(1, -1, 1, 1) + bias.view(1, -1, 1, 1)
    return x

class LayerNormExp2d(nn.LayerNorm):
    """ LayerNorm for channels_first tensors with 2d spatial dimensions (ie N, C, H, W).

    Experimental implementation w/ manual norm for tensors non-contiguous tensors.

    This improves throughput in some scenarios (tested on Ampere GPU), esp w/ channels_last
    layout. However, benefits are not always clear and can perform worse on other GPUs.
    """

    def __init__(self, num_channels, eps=1e-6):
        super().__init__(num_channels, eps=eps)

    def forward(self, x) -> torch.Tensor:
        if _is_contiguous(x):
            x = F.layer_norm(
                x.permute(0, 2, 3, 1), self.normalized_shape, self.weight, self.bias, self.eps).permute(0, 3, 1, 2)
        else:
            x = _layer_norm_cf(x, self.weight, self.bias, self.eps)
        return x
<|endoftext|>


<commit_before>import sys

sys.path.append('../')

import unittest
from bayesian import Bayes

class TestBayes(unittest.TestCase):
    def test_empty_constructor(self):
        with self.assertRaises(ValueError):
            b = Bayes()

    def test_list_constructor(self):
        self.assertEqual(Bayes([]), [])
        self.assertEqual(Bayes(()), [])
        self.assertEqual(Bayes(range(5)), [0, 1, 2, 3, 4])
        self.assertEqual(Bayes({'a': 10, 'b': 50}), [10, 50])
        self.assertEqual(Bayes([10, 10, 20]), [10, 10, 20])
        self.assertEqual(Bayes([('a', 10), ('b', 50)]), [10, 50])
        with self.assertRaises(ValueError):
            b = Bayes([('a', 10), ('b', 50), ('a', 15)])

    def test_get_odds(self):
        b = Bayes({'a': 10, 'b': 50})
        self.assertEqual(b['a'],   10)
        self.assertEqual(b['b'],   50)
        self.assertEqual(b[0], 10)
        self.assertEqual(b[1], 50)

        with self.assertRaises(IndexError):
            b[2]

        with self.assertRaises(ValueError):
            b['c']

    def test_set_odds(self):
        b = Bayes((10, 20, 30))
        b[0] = 50
        b[1] = 40
        b[2] = 30
        self.assertEqual(b, [50, 40, 30])

    def test_opposite(self):
        b = Bayes([0.2, 0.8])
        opposite = b.opposite()
        self.assertEqual(opposite[0] / opposite[1], b[1] / b[0])

        b = Bayes([0.2, 0.4, 0.4])
        opposite = b.opposite()
        self.assertEqual(opposite[0] / opposite[1], b[1] / b[0])
        self.assertEqual(opposite[1] / opposite[2], b[2] / b[1])
        self.assertEqual(opposite[0] / opposite[2], b[2] / b[0])

if __name__ == '__main__':
    unittest.main()
<commit_msg>Add `normalized` test<commit_after>import sys

sys.path.append('../')

import unittest
from bayesian import Bayes

class TestBayes(unittest.TestCase):
    def test_empty_constructor(self):
        with self.assertRaises(ValueError):
            b = Bayes()

    def test_list_constructor(self):
        self.assertEqual(Bayes([]), [])
        self.assertEqual(Bayes(()), [])
        self.assertEqual(Bayes(range(5)), [0, 1, 2, 3, 4])
        self.assertEqual(Bayes({'a': 10, 'b': 50}), [10, 50])
        self.assertEqual(Bayes([10, 10, 20]), [10, 10, 20])
        self.assertEqual(Bayes([('a', 10), ('b', 50)]), [10, 50])
        with self.assertRaises(ValueError):
            b = Bayes([('a', 10), ('b', 50), ('a', 15)])

    def test_get_odds(self):
        b = Bayes({'a': 10, 'b': 50})
        self.assertEqual(b['a'],   10)
        self.assertEqual(b['b'],   50)
        self.assertEqual(b[0], 10)
        self.assertEqual(b[1], 50)

        with self.assertRaises(IndexError):
            b[2]

        with self.assertRaises(ValueError):
            b['c']

    def test_set_odds(self):
        b = Bayes((10, 20, 30))
        b[0] = 50
        b[1] = 40
        b[2] = 30
        self.assertEqual(b, [50, 40, 30])

    def test_opposite(self):
        b = Bayes([0.2, 0.8])
        opposite = b.opposite()
        self.assertEqual(opposite[0] / opposite[1], b[1] / b[0])

        b = Bayes([0.2, 0.4, 0.4])
        opposite = b.opposite()
        self.assertEqual(opposite[0] / opposite[1], b[1] / b[0])
        self.assertEqual(opposite[1] / opposite[2], b[2] / b[1])
        self.assertEqual(opposite[0] / opposite[2], b[2] / b[0])

    def test_normalized(self):
        self.assertEqual(Bayes([]).normalized(), [])
        self.assertEqual(Bayes([2]).normalized(), [1])
        self.assertEqual(Bayes([9, 1]).normalized(), [0.9, 0.1])
        self.assertEqual(Bayes([2, 4, 4]).normalized(), [0.2, 0.4, 0.4])
        self.assertEqual(Bayes([2, 0]).normalized(), [1.0, 0])
        with self.assertRaises(ZeroDivisionError):
            self.assertEqual(Bayes([0, 0]).normalized(), [1.0, 0])

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


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

# FIXME: needs unittests
# FIXME: needs checks for out-of-order, concurrency, etc as attributes
from tinyrpc.exc import RPCError

class RPCServer(object):
    """High level RPC server.

    :param transport: The :py:class:`~tinyrpc.transports.RPCTransport` to use.
    :param protocol: The :py:class:`~tinyrpc.RPCProtocol` to use.
    :param dispatcher: The :py:class:`~tinyrpc.dispatch.RPCDispatcher` to use.
    """
    def __init__(self, transport, protocol, dispatcher):
        self.transport = transport
        self.protocol = protocol
        self.dispatcher = dispatcher

    def serve_forever(self):
        """Handle requests forever.

        Starts the server loop in which the transport will be polled for a new
        message.

        After a new message has arrived,
        :py:func:`~tinyrpc.server.RPCServer._spawn` is called with a handler
        function and arguments to handle the request.

        The handler function will try to decode the message using the supplied
        protocol, if that fails, an error response will be sent. After decoding
        the message, the dispatcher will be asked to handle the resultung
        request and the return value (either an error or a result) will be sent
        back to the client using the transport.

        After calling :py:func:`~tinyrpc.server.RPCServer._spawn`, the server
        will fetch the next message and repeat.
        """
        while True:
            context, message = self.transport.receive_message()

            # assuming protocol is threadsafe and dispatcher is theadsafe, as
            # long as its immutable

            def handle_message(context, message):
                try:
                    request = self.protocol.parse_request(message)
                except RPCError as e:
                    response = e.error_respond()
                else:
                    response = self.dispatcher.dispatch(request)

                # send reply
                self.transport.send_reply(context, response.serialize())

            self._spawn(handle_message, context, message)

    def _spawn(self, func, *args, **kwargs):
        """Spawn a handler function.

        This function is overridden in subclasses to provide concurrency.

        In the base implementation, it simply calls the supplied function
        ``func`` with ``*args`` and ``**kwargs``. This results in a
        single-threaded, single-process, synchronous server.

        :param func: A callable to call.
        :param args: Arguments to ``func``.
        :param kwargs: Keyword arguments to ``func``.
        """
        func(*args, **kwargs)
<commit_msg>- server.receive_one_message() (created from server_forever())<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# FIXME: needs unittests
# FIXME: needs checks for out-of-order, concurrency, etc as attributes
from tinyrpc.exc import RPCError

class RPCServer(object):
    """High level RPC server.

    :param transport: The :py:class:`~tinyrpc.transports.RPCTransport` to use.
    :param protocol: The :py:class:`~tinyrpc.RPCProtocol` to use.
    :param dispatcher: The :py:class:`~tinyrpc.dispatch.RPCDispatcher` to use.
    """
    def __init__(self, transport, protocol, dispatcher):
        self.transport = transport
        self.protocol = protocol
        self.dispatcher = dispatcher

    def serve_forever(self):
        """Handle requests forever.

        Starts the server loop in which the transport will be polled for a new
        message.

        After a new message has arrived,
        :py:func:`~tinyrpc.server.RPCServer._spawn` is called with a handler
        function and arguments to handle the request.

        The handler function will try to decode the message using the supplied
        protocol, if that fails, an error response will be sent. After decoding
        the message, the dispatcher will be asked to handle the resultung
        request and the return value (either an error or a result) will be sent
        back to the client using the transport.

        After calling :py:func:`~tinyrpc.server.RPCServer._spawn`, the server
        will fetch the next message and repeat.
        """
        while True:
            self.receive_one_message()

    def receive_one_message(self):
        context, message = self.transport.receive_message()

        # assuming protocol is threadsafe and dispatcher is theadsafe, as
        # long as its immutable

        def handle_message(context, message):
            try:
                request = self.protocol.parse_request(message)
            except RPCError as e:
                response = e.error_respond()
            else:
                response = self.dispatcher.dispatch(request)

            # send reply
            self.transport.send_reply(context, response.serialize())

        self._spawn(handle_message, context, message)

    def _spawn(self, func, *args, **kwargs):
        """Spawn a handler function.

        This function is overridden in subclasses to provide concurrency.

        In the base implementation, it simply calls the supplied function
        ``func`` with ``*args`` and ``**kwargs``. This results in a
        single-threaded, single-process, synchronous server.

        :param func: A callable to call.
        :param args: Arguments to ``func``.
        :param kwargs: Keyword arguments to ``func``.
        """
        func(*args, **kwargs)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Copyright (c) 2010 Martin S. <opensuse@sukimashita.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

import platform
import uuid
import aplog as log

from ZeroconfService import ZeroconfService
from plist import read_binary_plist
from airplayserver import BaseResource, AirPlaySite, IAirPlayServer

from twisted.application.service import MultiService
from twisted.application.internet import TCPServer
from twisted.web import error
from httplib import HTTPMessage
from cStringIO import StringIO

__all__ = [
    "AirPlayService",
]

CT_BINARY_PLIST = 'application/x-apple-binary-plist'

class PlaybackInfoResource(BaseResource):

    def render_GET(self, request):
        content = '<?xml version="1.0" encoding="UTF-8"?>\
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\
<plist version="1.0">\
<dict>\
<key>duration</key>\
<real>%f</real>\
<key>position</key>\
<real>%f</real>\
<key>rate</key>\
<real>%f</real>\
<key>playbackBufferEmpty</key>\
<%s/>\
<key>playbackBufferFull</key>\
<false/>\
<key>playbackLikelyToKeepUp</key>\
<true/>\
<key>readyToPlay</key>\
<%s/>\
<key>loadedTimeRanges</key>\
<array>\
    <dict>\
        <key>duration</key>\
        <real>%f</real>\
        <key>start</key>\
        <real>0.000000</real>\
    </dict>\
</array>\
<key>seekableTimeRanges</key>\
<array>\
    <dict>\
        <key>duration</key>\
        <real>%f</real>\
        <key>start</key>\
        <real>0.000000</real>\
    </dict>\
</array>\
</dict>\
</plist>'
        d, p = self.apserver.get_scrub()
        if (d+p == 0):
            playbackBufferEmpty = 'true'
            readyToPlay = 'false'
        else:
            playbackBufferEmpty = 'false'
            readyToPlay = 'true'

        content = content % (float(d), float(p), int(self.apserver.is_playing()), playbackBufferEmpty, readyToPlay, float(d), float(d))
        request.setHeader("Content-Type", "text/x-apple-plist+xml")
        return content

class PlayResource(BaseResource):

    def render_POST(self, request):
        parsedbody = self.parse_body(request.getAllHeaders(),
                                     request.content.read())

        # position may not be given for streaming media
        position = parsedbody['Start-Position'] if \
                parsedbody.has_key('Start-Position') else 0.0
        self.apserver.play(parsedbody['Content-Location'], float(position))
        return ""

    def parse_body(self, headers, body):
        ctype = headers.get('content-type')
        if ctype == CT_BINARY_PLIST:
            parsedbody = read_binary_plist(StringIO(body))
        else:
            parsedbody = HTTPMessage(StringIO(body))
        return parsedbody

class StopResource(BaseResource):

    def render_POST(self, request):
        self.apserver.stop()
        return ""

class ScrubResource(BaseResource):

    def render_GET(self, request):
        d, p = self.apserver.get_scrub()
        content = "duration: " + str(float(d))
        content += "\nposition: " + str(float(p))
        return content

    def render_POST(self, request):
        position = request.args['position'][0]
        self.apserver.set_scrub(float(position))
        return ""

class ReverseResource(BaseResource):

    def render_POST(self, request):
        self.apserver.reverse(None) #TODO
        request.setResponseCode(101)
        request.setHeader("Upgrade", "PTTH/1.0")
        request.setHeader("Connection", "Upgrade")
        return ""

class RateResource(BaseResource):

    def render_POST(self, request):
        value = request.args['value'][0]
        self.apserver.rate(float(value))
        return ""

class PhotoResource(BaseResource):

    def render_PUT(self, request):
        self.apserver.photo(request.content.read(), request.getHeader('X-Apple-Transition'))
        return ""

class ServerInfoResource(BaseResource):

    def __init__(self, ops, deviceid, features, model):
        BaseResource.__init__(self, ops)
        self.deviceid = deviceid
        self.features = features
        self.model = model

    def render_GET(self, request):
        content = '<?xml version="1.0" encoding="UTF-8"?>\
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\
<plist version="1.0">\
<dict>\
<key>deviceid</key>\
<string>%s</string>\
<key>features</key>\
<integer>%d</integer>\
<key>model</key>\
<string>%s</string>\
<key>protovers</key>\
<string>1.0</string>\
<key>srcvers</key>\
<string>101.10</string>\
</dict>\
</plist>'
        content = content % (self.deviceid, self.features, self.model)
        request.setHeader("Content-Type", "text/x-apple-plist+xml")
        return content

class SlideshowFeaturesResource(BaseResource):

    def render_GET(self, request):
        content = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>themes</key>
<array>
<dict>
<key>key</key>
<string>UPnP</string>
<key>name</key>
<string>UPnP</string>
</dict>
</array>
</dict>
</plist>"""
        request.setHeader("Content-Type", "text/x-apple-plist+xml")
        return content

class AirPlayService(MultiService):

    def __init__(self, apserver, name=None, host="0.0.0.0", port=22555):
        MultiService.__init__(self)

        self.apserver = IAirPlayServer(apserver)

        macstr = "%012X" % uuid.getnode()
        self.deviceid = ''.join("%s:" % macstr[i:i+2] for i in range(0, len(macstr), 2))[:-1]
        # 0x77 instead of 0x07 in order to support AirPlay from ordinary apps;
        # also means that the body for play will be a binary plist.
        self.features = 0x77
        self.model = "AppleTV2,1"

        # create TCP server
        TCPServer(port, self.create_site(), 100).setServiceParent(self)

        # create avahi service
        if (name is None):
            name = "Airplay Service on " + platform.node()
        zconf = ZeroconfService(name, port=port, stype="_airplay._tcp", text=["deviceid=" + self.deviceid, "features=" + hex(self.features), "model=" + self.model])
        zconf.setServiceParent(self)

        # for logging
        self.name_ = name
        self.host = host
        self.port = port

    def create_site(self):
        root = error.NoResource()
        root.putChild("playback-info", PlaybackInfoResource(self.apserver))
        root.putChild("play", PlayResource(self.apserver))
        root.putChild("stop", StopResource(self.apserver))
        root.putChild("scrub", ScrubResource(self.apserver))
        root.putChild("reverse", ReverseResource(self.apserver))
        root.putChild("rate", RateResource(self.apserver))
        root.putChild("photo", PhotoResource(self.apserver))
        root.putChild("slideshow-features", SlideshowFeaturesResource(self.apserver))
        root.putChild("server-info", ServerInfoResource(self.apserver, self.deviceid,
                                                        self.features,
                                                        self.model))
        return AirPlaySite(root)

    def startService(self):
        MultiService.startService(self)
        log.msg(1, "AirPlayService '%s' is running at %s:%d" % (self.name_, self.host,
                                                                self.port))
    def stopService(self):
        log.msg(1, "AirPlayService '%s' was stopped" % (self.name_, ))
        return MultiService.stopService(self)
<commit_msg>Changed AirPlay resources to use dynamically generated plists instead of format strings.<commit_after># -*- coding: utf-8 -*-
#
# Copyright (c) 2010 Martin S. <opensuse@sukimashita.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

import platform
import uuid
import aplog as log

from ZeroconfService import ZeroconfService
from plist import read_binary_plist
from airplayserver import BaseResource, AirPlaySite, IAirPlayServer

from twisted.application.service import MultiService
from twisted.application.internet import TCPServer
from twisted.web import error
from httplib import HTTPMessage
from cStringIO import StringIO
from plistlib import writePlistToString

__all__ = [
    "AirPlayService",
]

CT_BINARY_PLIST = 'application/x-apple-binary-plist'
CT_TEXT_PLIST = 'text/x-apple-plist+xml'

class PlaybackInfoResource(BaseResource):

    def render_GET(self, request):
        d, p = self.apserver.get_scrub()
        if (d+p == 0):
            playbackBufferEmpty = True
            readyToPlay = False
        else:
            playbackBufferEmpty = False
            readyToPlay = True

        info = {"duration": float(d),
                "position": float(p),
                "rate": int(self.apserver.is_playing()),
                "playbackBufferEmpty": playbackBufferEmpty,
                "playbackBufferFull": False,
                "playbackLikelyToKeepUp": True,
                "readyToPlay": readyToPlay,
                "loadedTimeRanges": [{"duration": float(d),
                                      "start": 0.0}],
                "seekableTimeRanges": [{"duration": float(d),
                                        "start": 0.0}]}

        content = writePlistToString(info)
        request.setHeader("Content-Type", CT_TEXT_PLIST)
        return content

class PlayResource(BaseResource):

    def render_POST(self, request):
        parsedbody = self.parse_body(request.getAllHeaders(),
                                     request.content.read())

        # position may not be given for streaming media
        position = parsedbody['Start-Position'] if \
                parsedbody.has_key('Start-Position') else 0.0
        self.apserver.play(parsedbody['Content-Location'], float(position))
        return ""

    def parse_body(self, headers, body):
        ctype = headers.get('content-type')
        if ctype == CT_BINARY_PLIST:
            parsedbody = read_binary_plist(StringIO(body))
        else:
            parsedbody = HTTPMessage(StringIO(body))
        return parsedbody

class StopResource(BaseResource):

    def render_POST(self, request):
        self.apserver.stop()
        return ""

class ScrubResource(BaseResource):

    def render_GET(self, request):
        d, p = self.apserver.get_scrub()
        content = "duration: " + str(float(d))
        content += "\nposition: " + str(float(p))
        return content

    def render_POST(self, request):
        position = request.args['position'][0]
        self.apserver.set_scrub(float(position))
        return ""

class ReverseResource(BaseResource):

    def render_POST(self, request):
        self.apserver.reverse(None) #TODO
        request.setResponseCode(101)
        request.setHeader("Upgrade", "PTTH/1.0")
        request.setHeader("Connection", "Upgrade")
        return ""

class RateResource(BaseResource):

    def render_POST(self, request):
        value = request.args['value'][0]
        self.apserver.rate(float(value))
        return ""

class PhotoResource(BaseResource):

    def render_PUT(self, request):
        self.apserver.photo(request.content.read(), request.getHeader('X-Apple-Transition'))
        return ""

class ServerInfoResource(BaseResource):

    def __init__(self, ops, deviceid, features, model):
        BaseResource.__init__(self, ops)
        self.deviceid = deviceid
        self.features = features
        self.model = model

    def render_GET(self, request):
        info = {"deviceid": self.deviceid,
                "features": int(self.features),
                "model": self.model,
                "protovers": "1.0",
                "srcvers": "101.10"}
        content = writePlistToString(info)
        request.setHeader("Content-Type", CT_TEXT_PLIST)
        return content

class SlideshowFeaturesResource(BaseResource):

    def render_GET(self, request):
        info = {"themes": [{"key": "UPnP",
                            "name": "UPnP"}]}
        content = writePlistToString(info)
        request.setHeader("Content-Type", CT_TEXT_PLIST)
        return content

class AirPlayService(MultiService):

    def __init__(self, apserver, name=None, host="0.0.0.0", port=22555):
        MultiService.__init__(self)

        self.apserver = IAirPlayServer(apserver)

        macstr = "%012X" % uuid.getnode()
        self.deviceid = ''.join("%s:" % macstr[i:i+2] for i in range(0, len(macstr), 2))[:-1]
        # 0x77 instead of 0x07 in order to support AirPlay from ordinary apps;
        # also means that the body for play will be a binary plist.
        self.features = 0x77
        self.model = "AppleTV2,1"

        # create TCP server
        TCPServer(port, self.create_site(), 100).setServiceParent(self)

        # create avahi service
        if (name is None):
            name = "Airplay Service on " + platform.node()
        zconf = ZeroconfService(name, port=port, stype="_airplay._tcp", text=["deviceid=" + self.deviceid, "features=" + hex(self.features), "model=" + self.model])
        zconf.setServiceParent(self)

        # for logging
        self.name_ = name
        self.host = host
        self.port = port

    def create_site(self):
        root = error.NoResource()
        root.putChild("playback-info", PlaybackInfoResource(self.apserver))
        root.putChild("play", PlayResource(self.apserver))
        root.putChild("stop", StopResource(self.apserver))
        root.putChild("scrub", ScrubResource(self.apserver))
        root.putChild("reverse", ReverseResource(self.apserver))
        root.putChild("rate", RateResource(self.apserver))
        root.putChild("photo", PhotoResource(self.apserver))
        root.putChild("slideshow-features", SlideshowFeaturesResource(self.apserver))
        root.putChild("server-info", ServerInfoResource(self.apserver, self.deviceid,
                                                        self.features,
                                                        self.model))
        return AirPlaySite(root)

    def startService(self):
        MultiService.startService(self)
        log.msg(1, "AirPlayService '%s' is running at %s:%d" % (self.name_, self.host,
                                                                self.port))
    def stopService(self):
        log.msg(1, "AirPlayService '%s' was stopped" % (self.name_, ))
        return MultiService.stopService(self)
<|endoftext|>


<commit_before>import requests
import sys
import time
import json

_DISCORD_API_BASE = "https://discordapp.com/api/v6"

def json_or_text(response):
    text = response.text
    if response.headers['content-type'] == 'application/json':
        return response.json()
    return text

class DiscordREST:
    def __init__(self, bot_token):
        self.bot_token = bot_token
        self.user_agent = "TitanEmbeds (https://github.com/EndenDragon/Titan) Python/{} requests/{}".format(sys.version_info, requests.__version__)
        self.rate_limit_bucket = {}
        self.global_limited = False
        self.global_limit_expire = 0

    def request(self, verb, url, **kwargs):
        headers = {
            'User-Agent': self.user_agent,
            'Authorization': 'Bot {}'.format(self.bot_token),
        }
        params = None
        if 'params' in kwargs:
            params = kwargs['params']
        data = None
        if 'data' in kwargs:
            data = kwargs['data']
        if 'json' in kwargs:
            headers['Content-Type'] = 'application/json'
            data = json.dumps(data)

        for tries in range(5):
            curepoch = time.time()
            if self.global_limited:
                time.sleep(self.global_limit_expire - curepoch)
                curepoch = time.time()

            if url in self.rate_limit_bucket and self.rate_limit_bucket[url] > curepoch:
                time.sleep(self.rate_limit_bucket[url] - curepoch)

            url_formatted = _DISCORD_API_BASE + url
            req = requests.request(verb, url_formatted, params=params, data=data, headers=headers)

            remaining = None
            if 'X-RateLimit-Remaining' in req.headers:
                remaining = req.headers['X-RateLimit-Remaining']
                if remaining == '0' and req.status_code != 429:
                    self.rate_limit_bucket[url] = int(req.headers['X-RateLimit-Reset'])

            if 300 > req.status_code >= 200:
                self.global_limited = False
                return {
                    'success': True,
                    'content': json_or_text(req),
                    'code': req.status_code,
                }

            if req.status_code == 429:
                if 'X-RateLimit-Global' not in req.headers:
                    self.rate_limit_bucket[url] = int(req.headers['X-RateLimit-Reset'])
                else:
                    self.global_limit_expire = time.time() + int(req.headers['Retry-After'])

            if req.status_code == 502 and tries <= 5:
                time.sleep(1 + tries * 2)
                continue

            if req.status_code == 403 or req.status_code == 404:
                return {
                    'success': False,
                    'code': req.status_code,
                }
        return {
            'success': False,
            'code': req.status_code,
            'content': json_or_text(req),
        }

    def get_all_guilds(self):
        _endpoint = "/users/@me/guilds"
        params = {}
        guilds = []
        count = 1 #priming the loop
        last_guild = ""
        while count > 0:
            r = self.request("GET", _endpoint, params=params)
            if r['success'] == True:
                content = r['content']
                count = len(content)
                guilds.extend(content)
                if count > 0:
                    params['after'] = content[-1]['id']
            else:
                count = 0
        return guilds

    def get_channel_messages(self, channel_id, after_snowflake=None):
        _endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
        params = {}
        if after_snowflake is not None:
            params = {'after': after_snowflake}
        r = self.request("GET", _endpoint, params=params)
        return r

    def create_message(self, channel_id, content):
        _endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
        payload = {'content': content}
        r = self.request("POST", _endpoint, data=payload)
        return r
<commit_msg>Added/Cleaned up rest methods. Also added a widget getter. Implemented caching to some endpoints<commit_after>import requests
import sys
import time
import json
from cachetools import cached, TTLCache

_DISCORD_API_BASE = "https://discordapp.com/api/v6"
get_widget_cache = TTLCache(200, 300)
guild_list_cache = TTLCache(1, 300) # Pop the list if new guilds are added

def json_or_text(response):
    text = response.text
    if response.headers['content-type'] == 'application/json':
        return response.json()
    return text

class DiscordREST:
    def __init__(self, bot_token):
        self.bot_token = bot_token
        self.user_agent = "TitanEmbeds (https://github.com/EndenDragon/Titan) Python/{} requests/{}".format(sys.version_info, requests.__version__)
        self.rate_limit_bucket = {}
        self.global_limited = False
        self.global_limit_expire = 0

    def request(self, verb, url, **kwargs):
        headers = {
            'User-Agent': self.user_agent,
            'Authorization': 'Bot {}'.format(self.bot_token),
        }
        params = None
        if 'params' in kwargs:
            params = kwargs['params']
        data = None
        if 'data' in kwargs:
            data = kwargs['data']
        if 'json' in kwargs:
            headers['Content-Type'] = 'application/json'
            data = json.dumps(data)

        for tries in range(5):
            curepoch = time.time()
            if self.global_limited:
                time.sleep(self.global_limit_expire - curepoch)
                curepoch = time.time()

            if url in self.rate_limit_bucket and self.rate_limit_bucket[url] > curepoch:
                time.sleep(self.rate_limit_bucket[url] - curepoch)

            url_formatted = _DISCORD_API_BASE + url
            req = requests.request(verb, url_formatted, params=params, data=data, headers=headers)

            remaining = None
            if 'X-RateLimit-Remaining' in req.headers:
                remaining = req.headers['X-RateLimit-Remaining']
                if remaining == '0' and req.status_code != 429:
                    self.rate_limit_bucket[url] = int(req.headers['X-RateLimit-Reset'])

            if 300 > req.status_code >= 200:
                self.global_limited = False
                return {
                    'success': True,
                    'content': json_or_text(req),
                    'code': req.status_code,
                }

            if req.status_code == 429:
                if 'X-RateLimit-Global' not in req.headers:
                    self.rate_limit_bucket[url] = int(req.headers['X-RateLimit-Reset'])
                else:
                    self.global_limit_expire = time.time() + int(req.headers['Retry-After'])

            if req.status_code == 502 and tries <= 5:
                time.sleep(1 + tries * 2)
                continue

            if req.status_code == 403 or req.status_code == 404:
                return {
                    'success': False,
                    'code': req.status_code,
                }
        return {
            'success': False,
            'code': req.status_code,
            'content': json_or_text(req),
        }

    #####################
    # Channel
    #####################

    def get_channel_messages(self, channel_id, after_snowflake=None):
        _endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
        params = {}
        if after_snowflake is not None:
            params = {'after': after_snowflake}
        r = self.request("GET", _endpoint, params=params)
        return r

    def create_message(self, channel_id, content):
        _endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
        payload = {'content': content}
        r = self.request("POST", _endpoint, data=payload)
        return r

    #####################
    # Guild
    #####################

    def get_guild_channels(self, guild_id):
        _endpoint = "/guilds/{guild_id}/channels".format(guild_id=guild_id)
        r = self.request("GET", _endpoint)
        return r

    def get_guild_roles(self, guild_id):
        _endpoint = "/guilds/{guild_id}/roles".format(guild_id=guild_id)
        r = self.request("GET", _endpoint)
        return r

    def get_guild_member(self, guild_id, user_id):
        _endpoint = "/guilds/{guild_id}/members/{user_id}".format(guild_id=guild_id, user_id=user_id)
        r = self.request("GET", _endpoint)
        return r

    def modify_guild_member(self, guild_id, user_id, **kwargs):
        _endpoint = "/guilds/{guild_id}/members/{user_id}".format(guild_id=guild_id, user_id=user_id)
        r = self.request("PATCH", _endpoint, data=kwargs, json=True)
        return r

    def get_guild_embed(self, guild_id):
        _endpoint = "/guilds/{guild_id}/embed".format(guild_id=guild_id)
        r = self.request("GET", _endpoint)
        return r

    def modify_guild_embed(self, guild_id, **kwargs):
        _endpoint = "/guilds/{guild_id}/embed".format(guild_id=guild_id)
        r = self.request("PATCH", _endpoint, data=kwargs, json=True)
        return r

    #####################
    # User
    #####################

    @cached(guild_list_cache)
    def get_all_guilds(self):
        _endpoint = "/users/@me/guilds"
        params = {}
        guilds = []
        count = 1 #priming the loop
        last_guild = ""
        while count > 0:
            r = self.request("GET", _endpoint, params=params)
            if r['success'] == True:
                content = r['content']
                count = len(content)
                guilds.extend(content)
                if count > 0:
                    params['after'] = content[-1]['id']
            else:
                count = 0
        return guilds

    #####################
    # Widget Handler
    #####################

    @cached(get_widget_cache)
    def get_widget(self, guild_id):
        _endpoint = _DISCORD_API_BASE + "/servers/{guild_id}/widget.json".format(guild_id=guild_id)
        embed = self.get_guild_embed(guild_id)
        if not embed['content']['enabled']:
            self.modify_guild_embed(guild_id, enabled=True, channel_id=guild_id)
        widget = requests.get(_endpoint).json()
        return widget
<|endoftext|>