<commit_before>import math

def clamp(x, minimum, maximum):
    # function f where f(x)=x if min<= x <= max, f(x) = minimum if x < minimum, f(x) = maximum if x > maximum
    return max(minimum, min(x, maximum))

class Vector2:
    def __init__(self, x=0.0, y=0.0, pb_vector=None):
        if pb_vector is not None:
            self.x = pb_vector.x
            self.y = pb_vector.y
        else:
            self.x = x
            self.y = y

    def __add__(self, other):
        return Vector2(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector2(self.x - other.x, self.y - other.y)

    def __neg__(self):
        return Vector2(-self.x, -self.y)

    def rotate(self, a):
        return Vector2(
          math.cos(a)*self.x - math.sin(a)*self.y,
          math.sin(a)*self.x + math.cos(a)*self.y)

    def abs(self):
        return math.sqrt(self.x*self.x + self.y*self.y)

    def __mul__(self, other):
        if isinstance(other, Vector2):
            # interpret multiplication as other^t * self (scalar product of the vectors, where ^t means transpose)
            return self.x*other.x + self.y*other.y
        elif isinstance(other, (int, float, long)):
            return Vector2(self.x*other, self.y*other)
        else:
            return NotImplemented

    def __rmul__(self, other):
        if isinstance(other, Vector2):
            # interpret multiplication as other^t * self (scalar product of the vectors, where ^t means transpose)
            return self.x * other.x + self.y * other.y
        elif isinstance(other, (int, float, long)):
            return Vector2(self.x * other, self.y * other)
        else:
            return NotImplemented

    # TODO python3: use __truediv__
    # https://docs.python.org/3/library/operator.html
    def __div__(self, fraction):
        return Vector2(self.x/fraction, self.y/fraction)

    def __truediv__(self, fraction):
        return Vector2(self.x/fraction, self.y/fraction)

    def __str__(self):
        return "({0},{1})".format(self.x, self.y)

    def __eq__(self, other):
        if self.x == other.x and self.y == other.y:
            return True
        else:
            return False

    def __getitem__(self, k):
        if k == 0:
            return self.x
        elif k == 1:
            return self.y
        else:
            raise IndexError()

    def normalize(self):
        if Vector2.abs(self) != 0:
            return self / Vector2.abs(self)
        else:
            return self

    def normalize_length(self, length):
        if self.x == 0 and self.y == 0:
            return Vector2(self.x, self.y)
        else:
            return (Vector2(self.x, self.y)*length) / self.abs()

    # TODO is this function really necessary?
    def rotate_right(self):
        x = self.x
        y = self.y
        return Vector2(y, -x)

    def angle(self):
        return math.atan2(self.y, self.x)

    def copy(self):
        return Vector2(self.x, self.y)

class Pose2D:
    def __init__(self, translation=Vector2(), rotation=0, pb_pose=None):
        if pb_pose is not None:
            self.translation = Vector2(pb_vector=pb_pose.translation)
            self.rotation = pb_pose.rotation
        else:
            self.translation = translation.copy()
            self.rotation = rotation

    def __mul__(self, other):
        if isinstance(other, Vector2):
            return other.rotate(self.rotation) + self.translation
        elif isinstance(other, Pose2D):
            p = Pose2D()
            p.translation = self*other.translation
            p.rotation = (self.rotation + other.rotation + math.pi) % (2*math.pi) - math.pi
            return p
        else:
            return NotImplemented

    def __invert__(self):
        return self.invert()

    def invert(self):
        p = Pose2D()
        p.rotation = -self.rotation
        p.translation = (Vector2() - self.translation).rotate(p.rotation)
        return p

    # operator method for '/' in python2
    def __div__(self, point):
        if isinstance(other, Vector2):
            return (point - self.translation).rotate(-self.rotation)
        else:
            return NotImplemented

    # operator method for '/' in python3
    def __truediv__(self, point):
        if isinstance(other, Vector2):
            return (point - self.translation).rotate(-self.rotation)
        else:
            return NotImplemented

    def __str__(self):
        return "(translation = {0}, rotation = {1})".format(self.translation, self.rotation)

    def rotate(self, a):
        self.rotation += a

    def translate(self, x, y):
        self.translation = self * Vector2(x, y)

    def copy(self):
        return Pose2D(self.translation.copy(), self.rotation)

class LineSegment(object):
    def __init__(self, begin=Vector2(), end=Vector2()):
        self.base = begin
        self.direction = end-self.base
        self.length = Vector2.abs(self.direction)
        if self.direction.abs() != 0:
            self.direction /= self.direction.abs()

    def __str__(self):
        return str("Begin: " + str(self.base)) + " End: " + str(self.base+self.direction*self.length)

    def begin(self):
        return self.base

    def end(self):
        return self.base+self.direction*self.length

    def point(self, t):
        # waere es nicht besser hier auch noch zu normieren, damit man t in [0,1] waehlen kann, mit t = 0 -> base point
        # t = 1 -> end point
        t = clamp(t, 0.0, self.length)
        return self.base + self.direction*t

    def project(self, p):
        return self.direction*p - self.direction*self.base

    def projection(self, p):
        t = self.direction * p - self.direction * self.base
        return self.point(t)

    def intersection(self, other):
        # Difference to line_intersection is the additional check if t is within the length of the linesegment.
        #HACK returns linesegment distance if intersection is not on linesegment

        normal = Vector2(-other.direction.y, other.direction.x)
        t = normal*self.direction
        if t == 0:
            return float('Inf')

        t = normal*(other.base-self.base)/t
        t = clamp(t, 0.0, self.length)

        return t

    def line_intersection(self, other):
        # maybe rename methods, this one and the one above; the return ist on how to scale the direction
        # vector to get the intersect point from the start (base) point. this is not derivable from the name
        normal = Vector2(-other.direction.y, other.direction.x)
        t = normal*self.direction
        if t == 0:
            return float('Inf')
        else:
            return normal*(other.base-self.base)/t

    def intersect(self, other):
        t = self.line_intersection(other)
        return 0.0 <= t <= self.length

class Rect2d:
    def __init__(self, a, b):
        self.minimum = Vector2(min(a.x, b.x), min(a.y, b.y))
        self.maximum = Vector2(max(a.x, b.x), max(a.y, b.y))

    def inside(self, p):
        return self.maximum.x >= p.x >= self.minimum.x and self.maximum.y >= p.y >= self.minimum.y

    def min(self):
        return self.minimum

    def max(self):
        return self.maximum
<commit_msg>use Numbers type to make it work in python 2x and 3x<commit_after>import math
import numbers

def clamp(x, minimum, maximum):
    # function f where f(x)=x if min<= x <= max, f(x) = minimum if x < minimum, f(x) = maximum if x > maximum
    return max(minimum, min(x, maximum))

class Vector2:
    def __init__(self, x=0.0, y=0.0, pb_vector=None):
        if pb_vector is not None:
            self.x = pb_vector.x
            self.y = pb_vector.y
        else:
            self.x = x
            self.y = y

    def __add__(self, other):
        return Vector2(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector2(self.x - other.x, self.y - other.y)

    def __neg__(self):
        return Vector2(-self.x, -self.y)

    def rotate(self, a):
        return Vector2(
          math.cos(a)*self.x - math.sin(a)*self.y,
          math.sin(a)*self.x + math.cos(a)*self.y)

    def abs(self):
        return math.sqrt(self.x*self.x + self.y*self.y)

    def __mul__(self, other):
        if isinstance(other, Vector2):
            # interpret multiplication as other^t * self (scalar product of the vectors, where ^t means transpose)
            return self.x*other.x + self.y*other.y
        elif isinstance(other, numbers.Number):
            return Vector2(self.x*other, self.y*other)
        else:
            return NotImplemented

    def __rmul__(self, other):
        if isinstance(other, Vector2):
            # interpret multiplication as other^t * self (scalar product of the vectors, where ^t means transpose)
            return self.x * other.x + self.y * other.y
        elif isinstance(other, numbers.Number):
            return Vector2(self.x * other, self.y * other)
        else:
            return NotImplemented

    # TODO python3: use __truediv__
    # https://docs.python.org/3/library/operator.html
    def __div__(self, fraction):
        return Vector2(self.x/fraction, self.y/fraction)

    def __truediv__(self, fraction):
        return Vector2(self.x/fraction, self.y/fraction)

    def __str__(self):
        return "({0},{1})".format(self.x, self.y)

    def __eq__(self, other):
        if self.x == other.x and self.y == other.y:
            return True
        else:
            return False

    def __getitem__(self, k):
        if k == 0:
            return self.x
        elif k == 1:
            return self.y
        else:
            raise IndexError()

    def normalize(self):
        if Vector2.abs(self) != 0:
            return self / Vector2.abs(self)
        else:
            return self

    def normalize_length(self, length):
        if self.x == 0 and self.y == 0:
            return Vector2(self.x, self.y)
        else:
            return (Vector2(self.x, self.y)*length) / self.abs()

    # TODO is this function really necessary?
    def rotate_right(self):
        x = self.x
        y = self.y
        return Vector2(y, -x)

    def angle(self):
        return math.atan2(self.y, self.x)

    def copy(self):
        return Vector2(self.x, self.y)

class Pose2D:
    def __init__(self, translation=Vector2(), rotation=0, pb_pose=None):
        if pb_pose is not None:
            self.translation = Vector2(pb_vector=pb_pose.translation)
            self.rotation = pb_pose.rotation
        else:
            self.translation = translation.copy()
            self.rotation = rotation

    def __mul__(self, other):
        if isinstance(other, Vector2):
            return other.rotate(self.rotation) + self.translation
        elif isinstance(other, Pose2D):
            p = Pose2D()
            p.translation = self*other.translation
            p.rotation = (self.rotation + other.rotation + math.pi) % (2*math.pi) - math.pi
            return p
        else:
            return NotImplemented

    def __invert__(self):
        return self.invert()

    def invert(self):
        p = Pose2D()
        p.rotation = -self.rotation
        p.translation = (Vector2() - self.translation).rotate(p.rotation)
        return p

    # operator method for '/' in python2
    def __div__(self, point):
        if isinstance(point, Vector2):
            return (point - self.translation).rotate(-self.rotation)
        else:
            return NotImplemented

    # operator method for '/' in python3
    def __truediv__(self, point):
        if isinstance(point, Vector2):
            return (point - self.translation).rotate(-self.rotation)
        else:
            return NotImplemented

    def __str__(self):
        return "(translation = {0}, rotation = {1})".format(self.translation, self.rotation)

    def rotate(self, a):
        self.rotation += a

    def translate(self, x, y):
        self.translation = self * Vector2(x, y)

    def copy(self):
        return Pose2D(self.translation.copy(), self.rotation)

class LineSegment(object):
    def __init__(self, begin=Vector2(), end=Vector2()):
        self.base = begin
        self.direction = end-self.base
        self.length = Vector2.abs(self.direction)
        if self.direction.abs() != 0:
            self.direction /= self.direction.abs()

    def __str__(self):
        return str("Begin: " + str(self.base)) + " End: " + str(self.base+self.direction*self.length)

    def begin(self):
        return self.base

    def end(self):
        return self.base+self.direction*self.length

    def point(self, t):
        # waere es nicht besser hier auch noch zu normieren, damit man t in [0,1] waehlen kann, mit t = 0 -> base point
        # t = 1 -> end point
        t = clamp(t, 0.0, self.length)
        return self.base + self.direction*t

    def project(self, p):
        return self.direction*p - self.direction*self.base

    def projection(self, p):
        t = self.direction * p - self.direction * self.base
        return self.point(t)

    def intersection(self, other):
        # Difference to line_intersection is the additional check if t is within the length of the linesegment.
        #HACK returns linesegment distance if intersection is not on linesegment

        normal = Vector2(-other.direction.y, other.direction.x)
        t = normal*self.direction
        if t == 0:
            return float('Inf')

        t = normal*(other.base-self.base)/t
        t = clamp(t, 0.0, self.length)

        return t

    def line_intersection(self, other):
        # maybe rename methods, this one and the one above; the return ist on how to scale the direction
        # vector to get the intersect point from the start (base) point. this is not derivable from the name
        normal = Vector2(-other.direction.y, other.direction.x)
        t = normal*self.direction
        if t == 0:
            return float('Inf')
        else:
            return normal*(other.base-self.base)/t

    def intersect(self, other):
        t = self.line_intersection(other)
        return 0.0 <= t <= self.length

class Rect2d:
    def __init__(self, a, b):
        self.minimum = Vector2(min(a.x, b.x), min(a.y, b.y))
        self.maximum = Vector2(max(a.x, b.x), max(a.y, b.y))

    def inside(self, p):
        return self.maximum.x >= p.x >= self.minimum.x and self.maximum.y >= p.y >= self.minimum.y

    def min(self):
        return self.minimum

    def max(self):
        return self.maximum
<|endoftext|>


<commit_before>from datetime import datetime
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.core.cache import cache
from django.db.models import Count
from piston.handler import BaseHandler
from piston.utils import rc
from knesset.mks.models import Member, Party, Membership
from knesset.laws.models import Vote, VoteAction
from tagging.models import Tag, TaggedItem
import math

DEFAULT_PAGE_LEN = 20
def limit_by_request(qs, request):
    if 'num' in request.GET:
        num = int(request.GET['num'])
        page = 'page' in request.GET and int(request.GET['page']) or 0
        return qs[page*num:(page+1)*num]
    return qs

class MemberHandler(BaseHandler):
    fields = ('id', 'url', 'name','party', 'img_url', 'votes_count', 'votes_per_month', 'service_time', 'discipline','average_weekly_presence', 'committee_meetings_per_month','bills_proposed','bills_passed_pre_vote','bills_passed_first_vote','bills_approved', 'roles', 'average_weekly_presence_rank', 'committees', )
    allowed_methods = ('GET')
    model = Member
    qs = Member.objects.all()

    @classmethod
    def url (self, member):
        return member.get_absolute_url()

    @classmethod
    def party (self, member):
        return member.current_party.name

    @classmethod
    def votes_count (self, member):
        return member.voting_statistics.votes_count()

    @classmethod
    def votes_per_month (self, member):
        return round(member.voting_statistics.average_votes_per_month(),1)

    @classmethod
    def service_time (self, member):
        return member.service_time()

    @classmethod
    def discipline (self, member):
        x = member.voting_statistics.discipline()
        if x:
            return round(x,2)
        else:
            return None

    @classmethod
    def bills_proposed(self, member):
        return member.bills.count()

    @classmethod
    def bills_passed_pre_vote(self, member):
        return member.bills.filter(Q(stage='2')|Q(stage='3')|Q(stage='4')|Q(stage='5')|Q(stage='6')).count()

    @classmethod
    def bills_passed_first_vote(self, member):
        return member.bills.filter(Q(stage='4')|Q(stage='5')|Q(stage='6')).count()

    @classmethod
    def bills_approved(self, member):
        return member.bills.filter(stage='6').count()

    @classmethod
    def roles (self, member):
        return member.current_role_descriptions or ''

    @classmethod
    def average_weekly_presence_rank (self, member):
        ''' Calculate the distribution of presence and place the user on a 5 level scale '''
        SCALE = 5

        rel_location = cache.get('average_presence_location_%d' % member.id)
        if not rel_location:

            presence_list = sorted(map(lambda member: member.average_weekly_presence(), Member.objects.all()))
            presence_groups = int(math.ceil(len(presence_list) / float(SCALE)))

            # Generate cache for all members
            for mk in Member.objects.all():
                avg = mk.average_weekly_presence()
                if avg:
                    mk_location = 1 + (presence_list.index(avg) / presence_groups)
                else:
                    mk_location = 0

                cache.set('average_presence_location_%d' % mk.id, mk_location, 60*60*24)

                if mk.id == member.id:
                    rel_location = mk_location

        return rel_location

    @classmethod
    def committees (self, member):
        temp_list = member.committee_meetings.values("committee", "committee__name").annotate(Count("id")).order_by('-id__count')[:5]
        return (map(lambda item: (item['committee__name'], reverse('committee-detail', args=[item['committee']])), temp_list))

    @classmethod
    def member (self, member):
        qs = self.qs.filter(member=member)
        return map(lambda o: dict(url=o.party.get_absolute_url(),
                     name=o.party.name,
                     since=o.start_date,
                     until=o.end_date,
                     ), qs)

class VoteHandler(BaseHandler):
    fields = ('url', 'title', 'time',
              'summary','full_text',
              'for_votes', 'against_votes' , 'abstain_votes' , 'didnt_vote' ,
             )
    exclude = ('member')
    allowed_methods = ('GET',)
    model = Vote
    qs = Vote.objects.all()

    def read(self, request, **kwargs):
        ''' returns a vote or a list of votes '''
        qs = self.qs

        if 'id' in kwargs:
            return super(VoteHandler, self).read(request, **kwargs)

        type = request.GET.get('type', None)
        order = request.GET.get('order', None)
        days_back = request.GET.get('days_back', None)
        page_len = request.GET.get('page_len', DEFAULT_PAGE_LEN)
        page_num=request.GET.get('page_num', 0)

        if type:
            qs = qs.filter(title__contains=type)
        if days_back:
            qs = qs.since(days=days_back)
        if order:
            qs = qs.sort(by=order)
        return qs[page_len*page_num:page_len*(page_num +1)]

    @classmethod
    def url(self, vote):
        return vote.get_absolute_url()

    @classmethod
    def for_votes(self, vote):
        return vote.get_voters_id('for')

    @classmethod
    def against_votes(self, vote):
        return vote.get_voters_id('against')

    @classmethod
    def abstain_votes(self, vote):
        return vote.get_voters_id('abstain')

    @classmethod
    def didnt_vote(self, vote):
        return vote.get_voters_id('no-vote')

class PartyHandler(BaseHandler):
    fields = ('id', 'name', 'start_date', 'end_date')
    allowed_methods = ('GET',)
    model = Party

    def read(self, request, id=None):
        if id:
            return Party.objects.filter(pk=id)
        else:
            return Party.objects.all()

class TagHandler(BaseHandler):
    fields = ('id', 'name', 'number_of_items')
    allowed_methods = ('GET',)
    model = Tag

    def read(self, request, **kwargs):
        id = None
        if 'id' in kwargs:
            id = kwargs['id']
        if id:
            return Tag.objects.filter(pk=id)
        object_id = None
        ctype = None
        if 'object_id' in kwargs and 'object_type' in kwargs:
            object_id = kwargs['object_id']
            try:
                ctype = ContentType.objects.get(model=kwargs['object_type'])
            except ContentType.DoesNotExist:
                pass
        if object_id and ctype:
            tags_ids = TaggedItem.objects.filter(object_id=object_id).filter(content_type=ctype).values_list('tag', flat=True)
            return Tag.objects.filter(id__in=tags_ids)

        return Tag.objects.usage_for_model(Vote)

    @classmethod
    def number_of_items(self, tag):
        return tag.items.count()

<commit_msg>API returns role using "get_role" instead of directly accessing the field<commit_after>from datetime import datetime
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.core.cache import cache
from django.db.models import Count
from piston.handler import BaseHandler
from piston.utils import rc
from knesset.mks.models import Member, Party, Membership
from knesset.laws.models import Vote, VoteAction
from tagging.models import Tag, TaggedItem
import math

DEFAULT_PAGE_LEN = 20
def limit_by_request(qs, request):
    if 'num' in request.GET:
        num = int(request.GET['num'])
        page = 'page' in request.GET and int(request.GET['page']) or 0
        return qs[page*num:(page+1)*num]
    return qs

class MemberHandler(BaseHandler):
    fields = ('id', 'url', 'name','party', 'img_url', 'votes_count', 'votes_per_month', 'service_time', 'discipline','average_weekly_presence', 'committee_meetings_per_month','bills_proposed','bills_passed_pre_vote','bills_passed_first_vote','bills_approved', 'roles', 'average_weekly_presence_rank', 'committees', )
    allowed_methods = ('GET')
    model = Member
    qs = Member.objects.all()

    @classmethod
    def url (self, member):
        return member.get_absolute_url()

    @classmethod
    def party (self, member):
        return member.current_party.name

    @classmethod
    def votes_count (self, member):
        return member.voting_statistics.votes_count()

    @classmethod
    def votes_per_month (self, member):
        return round(member.voting_statistics.average_votes_per_month(),1)

    @classmethod
    def service_time (self, member):
        return member.service_time()

    @classmethod
    def discipline (self, member):
        x = member.voting_statistics.discipline()
        if x:
            return round(x,2)
        else:
            return None

    @classmethod
    def bills_proposed(self, member):
        return member.bills.count()

    @classmethod
    def bills_passed_pre_vote(self, member):
        return member.bills.filter(Q(stage='2')|Q(stage='3')|Q(stage='4')|Q(stage='5')|Q(stage='6')).count()

    @classmethod
    def bills_passed_first_vote(self, member):
        return member.bills.filter(Q(stage='4')|Q(stage='5')|Q(stage='6')).count()

    @classmethod
    def bills_approved(self, member):
        return member.bills.filter(stage='6').count()

    @classmethod
    def roles (self, member):
        return member.get_role

    @classmethod
    def average_weekly_presence_rank (self, member):
        ''' Calculate the distribution of presence and place the user on a 5 level scale '''
        SCALE = 5

        rel_location = cache.get('average_presence_location_%d' % member.id)
        if not rel_location:

            presence_list = sorted(map(lambda member: member.average_weekly_presence(), Member.objects.all()))
            presence_groups = int(math.ceil(len(presence_list) / float(SCALE)))

            # Generate cache for all members
            for mk in Member.objects.all():
                avg = mk.average_weekly_presence()
                if avg:
                    mk_location = 1 + (presence_list.index(avg) / presence_groups)
                else:
                    mk_location = 0

                cache.set('average_presence_location_%d' % mk.id, mk_location, 60*60*24)

                if mk.id == member.id:
                    rel_location = mk_location

        return rel_location

    @classmethod
    def committees (self, member):
        temp_list = member.committee_meetings.values("committee", "committee__name").annotate(Count("id")).order_by('-id__count')[:5]
        return (map(lambda item: (item['committee__name'], reverse('committee-detail', args=[item['committee']])), temp_list))

    @classmethod
    def member (self, member):
        qs = self.qs.filter(member=member)
        return map(lambda o: dict(url=o.party.get_absolute_url(),
                     name=o.party.name,
                     since=o.start_date,
                     until=o.end_date,
                     ), qs)

class VoteHandler(BaseHandler):
    fields = ('url', 'title', 'time',
              'summary','full_text',
              'for_votes', 'against_votes' , 'abstain_votes' , 'didnt_vote' ,
             )
    exclude = ('member')
    allowed_methods = ('GET',)
    model = Vote
    qs = Vote.objects.all()

    def read(self, request, **kwargs):
        ''' returns a vote or a list of votes '''
        qs = self.qs

        if 'id' in kwargs:
            return super(VoteHandler, self).read(request, **kwargs)

        type = request.GET.get('type', None)
        order = request.GET.get('order', None)
        days_back = request.GET.get('days_back', None)
        page_len = request.GET.get('page_len', DEFAULT_PAGE_LEN)
        page_num=request.GET.get('page_num', 0)

        if type:
            qs = qs.filter(title__contains=type)
        if days_back:
            qs = qs.since(days=days_back)
        if order:
            qs = qs.sort(by=order)
        return qs[page_len*page_num:page_len*(page_num +1)]

    @classmethod
    def url(self, vote):
        return vote.get_absolute_url()

    @classmethod
    def for_votes(self, vote):
        return vote.get_voters_id('for')

    @classmethod
    def against_votes(self, vote):
        return vote.get_voters_id('against')

    @classmethod
    def abstain_votes(self, vote):
        return vote.get_voters_id('abstain')

    @classmethod
    def didnt_vote(self, vote):
        return vote.get_voters_id('no-vote')

class PartyHandler(BaseHandler):
    fields = ('id', 'name', 'start_date', 'end_date')
    allowed_methods = ('GET',)
    model = Party

    def read(self, request, id=None):
        if id:
            return Party.objects.filter(pk=id)
        else:
            return Party.objects.all()

class TagHandler(BaseHandler):
    fields = ('id', 'name', 'number_of_items')
    allowed_methods = ('GET',)
    model = Tag

    def read(self, request, **kwargs):
        id = None
        if 'id' in kwargs:
            id = kwargs['id']
        if id:
            return Tag.objects.filter(pk=id)
        object_id = None
        ctype = None
        if 'object_id' in kwargs and 'object_type' in kwargs:
            object_id = kwargs['object_id']
            try:
                ctype = ContentType.objects.get(model=kwargs['object_type'])
            except ContentType.DoesNotExist:
                pass
        if object_id and ctype:
            tags_ids = TaggedItem.objects.filter(object_id=object_id).filter(content_type=ctype).values_list('tag', flat=True)
            return Tag.objects.filter(id__in=tags_ids)

        return Tag.objects.usage_for_model(Vote)

    @classmethod
    def number_of_items(self, tag):
        return tag.items.count()

<|endoftext|>


<commit_before>"""
Allow to setup simple automation rules via the config file.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
"""
import asyncio
from functools import partial
import logging
import os

import voluptuous as vol

from homeassistant.bootstrap import async_prepare_setup_platform
from homeassistant import config as conf_util
from homeassistant.const import (
    ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
    SERVICE_TOGGLE)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv

DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'

DEPENDENCIES = ['group']

GROUP_NAME_ALL_AUTOMATIONS = 'all automations'

CONF_ALIAS = 'alias'
CONF_HIDE_ENTITY = 'hide_entity'

CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONF_INITIAL_STATE = 'initial_state'

CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
CONDITION_TYPE_OR = 'or'

DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
DEFAULT_HIDE_ENTITY = False
DEFAULT_INITIAL_STATE = True

ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
SERVICE_TRIGGER = 'trigger'
SERVICE_RELOAD = 'reload'

_LOGGER = logging.getLogger(__name__)

def _platform_validator(config):
    """Validate it is a valid  platform."""
    platform = get_platform(DOMAIN, config[CONF_PLATFORM])

    if not hasattr(platform, 'TRIGGER_SCHEMA'):
        return config

    return getattr(platform, 'TRIGGER_SCHEMA')(config)

_TRIGGER_SCHEMA = vol.All(
    cv.ensure_list,
    [
        vol.All(
            vol.Schema({
                vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN)
            }, extra=vol.ALLOW_EXTRA),
            _platform_validator
        ),
    ]
)

_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])

PLATFORM_SCHEMA = vol.Schema({
    CONF_ALIAS: cv.string,
    vol.Optional(CONF_INITIAL_STATE,
                 default=DEFAULT_INITIAL_STATE): cv.boolean,
    vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
    vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
    vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
    vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})

SERVICE_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

TRIGGER_SERVICE_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Optional(ATTR_VARIABLES, default={}): dict,
})

RELOAD_SERVICE_SCHEMA = vol.Schema({})

def is_on(hass, entity_id=None):
    """
    Return true if specified automation entity_id is on.

    Check all automation if no entity_id specified.
    """
    entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
    return any(hass.states.is_state(entity_id, STATE_ON)
               for entity_id in entity_ids)

def turn_on(hass, entity_id=None):
    """Turn on specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TURN_ON, data)

def turn_off(hass, entity_id=None):
    """Turn off specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)

def toggle(hass, entity_id=None):
    """Toggle specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TOGGLE, data)

def trigger(hass, entity_id=None):
    """Trigger specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TRIGGER, data)

def reload(hass):
    """Reload the automation from config."""
    hass.services.call(DOMAIN, SERVICE_RELOAD)

@asyncio.coroutine
def async_setup(hass, config):
    """Setup the automation."""
    component = EntityComponent(_LOGGER, DOMAIN, hass,
                                group_name=GROUP_NAME_ALL_AUTOMATIONS)

    success = yield from _async_process_config(hass, config, component)

    if not success:
        return False

    descriptions = yield from hass.loop.run_in_executor(
        None, conf_util.load_yaml_config_file, os.path.join(
            os.path.dirname(__file__), 'services.yaml')
    )

    @asyncio.coroutine
    def trigger_service_handler(service_call):
        """Handle automation triggers."""
        tasks = []
        for entity in component.async_extract_from_service(service_call):
            tasks.append(entity.async_trigger(
                service_call.data.get(ATTR_VARIABLES), True))
        yield from asyncio.wait(tasks, loop=hass.loop)

    @asyncio.coroutine
    def turn_onoff_service_handler(service_call):
        """Handle automation turn on/off service calls."""
        tasks = []
        method = 'async_{}'.format(service_call.service)
        for entity in component.async_extract_from_service(service_call):
            tasks.append(getattr(entity, method)())
        yield from asyncio.wait(tasks, loop=hass.loop)

    @asyncio.coroutine
    def toggle_service_handler(service_call):
        """Handle automation toggle service calls."""
        tasks = []
        for entity in component.async_extract_from_service(service_call):
            if entity.is_on:
                tasks.append(entity.async_turn_off())
            else:
                tasks.append(entity.async_turn_on())
        yield from asyncio.wait(tasks, loop=hass.loop)

    @asyncio.coroutine
    def reload_service_handler(service_call):
        """Remove all automations and load new ones from config."""
        conf = yield from component.async_prepare_reload()
        if conf is None:
            return
        yield from _async_process_config(hass, conf, component)

    hass.services.async_register(
        DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
        descriptions.get(SERVICE_TRIGGER), schema=TRIGGER_SERVICE_SCHEMA)

    hass.services.async_register(
        DOMAIN, SERVICE_RELOAD, reload_service_handler,
        descriptions.get(SERVICE_RELOAD), schema=RELOAD_SERVICE_SCHEMA)

    hass.services.async_register(
        DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
        descriptions.get(SERVICE_TOGGLE), schema=SERVICE_SCHEMA)

    for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
        hass.services.async_register(
            DOMAIN, service, turn_onoff_service_handler,
            descriptions.get(service), schema=SERVICE_SCHEMA)

    return True

class AutomationEntity(ToggleEntity):
    """Entity to show status of entity."""

    def __init__(self, name, async_attach_triggers, cond_func, async_action,
                 hidden):
        """Initialize an automation entity."""
        self._name = name
        self._async_attach_triggers = async_attach_triggers
        self._async_detach_triggers = None
        self._cond_func = cond_func
        self._async_action = async_action
        self._enabled = False
        self._last_triggered = None
        self._hidden = hidden

    @property
    def name(self):
        """Name of the automation."""
        return self._name

    @property
    def should_poll(self):
        """No polling needed for automation entities."""
        return False

    @property
    def state_attributes(self):
        """Return the entity state attributes."""
        return {
            ATTR_LAST_TRIGGERED: self._last_triggered
        }

    @property
    def hidden(self) -> bool:
        """Return True if the automation entity should be hidden from UIs."""
        return self._hidden

    @property
    def is_on(self) -> bool:
        """Return True if entity is on."""
        return self._enabled

    @asyncio.coroutine
    def async_turn_on(self, **kwargs) -> None:
        """Turn the entity on and update the state."""
        if self._enabled:
            return

        yield from self.async_enable()
        yield from self.async_update_ha_state()

    @asyncio.coroutine
    def async_turn_off(self, **kwargs) -> None:
        """Turn the entity off."""
        if not self._enabled:
            return

        self._async_detach_triggers()
        self._async_detach_triggers = None
        self._enabled = False
        yield from self.async_update_ha_state()

    @asyncio.coroutine
    def async_trigger(self, variables, skip_condition=False):
        """Trigger automation.

        This method is a coroutine.
        """
        if skip_condition or self._cond_func(variables):
            yield from self._async_action(self.entity_id, variables)
            self._last_triggered = utcnow()
            yield from self.async_update_ha_state()

    @asyncio.coroutine
    def async_remove(self):
        """Remove automation from HASS."""
        yield from self.async_turn_off()
        yield from super().async_remove()

    @asyncio.coroutine
    def async_enable(self):
        """Enable this automation entity.

        This method is a coroutine.
        """
        if self._enabled:
            return

        self._async_detach_triggers = yield from self._async_attach_triggers(
            self.async_trigger)
        self._enabled = True

@asyncio.coroutine
def _async_process_config(hass, config, component):
    """Process config and add automations.

    This method is a coroutine.
    """
    entities = []
    tasks = []

    for config_key in extract_domain_configs(config, DOMAIN):
        conf = config[config_key]

        for list_no, config_block in enumerate(conf):
            name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
                                                                  list_no)

            hidden = config_block[CONF_HIDE_ENTITY]

            action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
                                       name)

            if CONF_CONDITION in config_block:
                cond_func = _async_process_if(hass, config, config_block)

                if cond_func is None:
                    continue
            else:
                def cond_func(variables):
                    """Condition will always pass."""
                    return True

            async_attach_triggers = partial(
                _async_process_trigger, hass, config,
                config_block.get(CONF_TRIGGER, []), name)
            entity = AutomationEntity(name, async_attach_triggers, cond_func,
                                      action, hidden)
            if config_block[CONF_INITIAL_STATE]:
                tasks.append(entity.async_enable())
            entities.append(entity)

    if tasks:
        yield from asyncio.wait(tasks, loop=hass.loop)
    if entities:
        yield from component.async_add_entities(entities)

    return len(entities) > 0

def _async_get_action(hass, config, name):
    """Return an action based on a configuration."""
    script_obj = script.Script(hass, config, name)

    @asyncio.coroutine
    def action(entity_id, variables):
        """Action to be executed."""
        _LOGGER.info('Executing %s', name)
        logbook.async_log_entry(
            hass, name, 'has been triggered', DOMAIN, entity_id)
        yield from script_obj.async_run(variables)

    return action

def _async_process_if(hass, config, p_config):
    """Process if checks."""
    if_configs = p_config.get(CONF_CONDITION)

    checks = []
    for if_config in if_configs:
        try:
            checks.append(condition.async_from_config(if_config, False))
        except HomeAssistantError as ex:
            _LOGGER.warning('Invalid condition: %s', ex)
            return None

    def if_action(variables=None):
        """AND all conditions."""
        return all(check(hass, variables) for check in checks)

    return if_action

@asyncio.coroutine
def _async_process_trigger(hass, config, trigger_configs, name, action):
    """Setup the triggers.

    This method is a coroutine.
    """
    removes = []

    for conf in trigger_configs:
        platform = yield from async_prepare_setup_platform(
            hass, config, DOMAIN, conf.get(CONF_PLATFORM))

        if platform is None:
            return None

        remove = platform.async_trigger(hass, conf, action)

        if not remove:
            _LOGGER.error("Error setting up trigger %s", name)
            continue

        _LOGGER.info("Initialized trigger %s", name)
        removes.append(remove)

    if not removes:
        return None

    def remove_triggers():
        """Remove attached triggers."""
        for remove in removes:
            remove()

    return remove_triggers
<commit_msg>Bugfix wait in automation (#4984)<commit_after>"""
Allow to setup simple automation rules via the config file.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
"""
import asyncio
from functools import partial
import logging
import os

import voluptuous as vol

from homeassistant.bootstrap import async_prepare_setup_platform
from homeassistant import config as conf_util
from homeassistant.const import (
    ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
    SERVICE_TOGGLE)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv

DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'

DEPENDENCIES = ['group']

GROUP_NAME_ALL_AUTOMATIONS = 'all automations'

CONF_ALIAS = 'alias'
CONF_HIDE_ENTITY = 'hide_entity'

CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONF_INITIAL_STATE = 'initial_state'

CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
CONDITION_TYPE_OR = 'or'

DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
DEFAULT_HIDE_ENTITY = False
DEFAULT_INITIAL_STATE = True

ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
SERVICE_TRIGGER = 'trigger'
SERVICE_RELOAD = 'reload'

_LOGGER = logging.getLogger(__name__)

def _platform_validator(config):
    """Validate it is a valid  platform."""
    platform = get_platform(DOMAIN, config[CONF_PLATFORM])

    if not hasattr(platform, 'TRIGGER_SCHEMA'):
        return config

    return getattr(platform, 'TRIGGER_SCHEMA')(config)

_TRIGGER_SCHEMA = vol.All(
    cv.ensure_list,
    [
        vol.All(
            vol.Schema({
                vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN)
            }, extra=vol.ALLOW_EXTRA),
            _platform_validator
        ),
    ]
)

_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])

PLATFORM_SCHEMA = vol.Schema({
    CONF_ALIAS: cv.string,
    vol.Optional(CONF_INITIAL_STATE,
                 default=DEFAULT_INITIAL_STATE): cv.boolean,
    vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
    vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
    vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
    vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})

SERVICE_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

TRIGGER_SERVICE_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Optional(ATTR_VARIABLES, default={}): dict,
})

RELOAD_SERVICE_SCHEMA = vol.Schema({})

def is_on(hass, entity_id=None):
    """
    Return true if specified automation entity_id is on.

    Check all automation if no entity_id specified.
    """
    entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
    return any(hass.states.is_state(entity_id, STATE_ON)
               for entity_id in entity_ids)

def turn_on(hass, entity_id=None):
    """Turn on specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TURN_ON, data)

def turn_off(hass, entity_id=None):
    """Turn off specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)

def toggle(hass, entity_id=None):
    """Toggle specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TOGGLE, data)

def trigger(hass, entity_id=None):
    """Trigger specified automation or all."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_TRIGGER, data)

def reload(hass):
    """Reload the automation from config."""
    hass.services.call(DOMAIN, SERVICE_RELOAD)

@asyncio.coroutine
def async_setup(hass, config):
    """Setup the automation."""
    component = EntityComponent(_LOGGER, DOMAIN, hass,
                                group_name=GROUP_NAME_ALL_AUTOMATIONS)

    success = yield from _async_process_config(hass, config, component)

    if not success:
        return False

    descriptions = yield from hass.loop.run_in_executor(
        None, conf_util.load_yaml_config_file, os.path.join(
            os.path.dirname(__file__), 'services.yaml')
    )

    @asyncio.coroutine
    def trigger_service_handler(service_call):
        """Handle automation triggers."""
        tasks = []
        for entity in component.async_extract_from_service(service_call):
            tasks.append(entity.async_trigger(
                service_call.data.get(ATTR_VARIABLES), True))

        if tasks:
            yield from asyncio.wait(tasks, loop=hass.loop)

    @asyncio.coroutine
    def turn_onoff_service_handler(service_call):
        """Handle automation turn on/off service calls."""
        tasks = []
        method = 'async_{}'.format(service_call.service)
        for entity in component.async_extract_from_service(service_call):
            tasks.append(getattr(entity, method)())

        if tasks:
            yield from asyncio.wait(tasks, loop=hass.loop)

    @asyncio.coroutine
    def toggle_service_handler(service_call):
        """Handle automation toggle service calls."""
        tasks = []
        for entity in component.async_extract_from_service(service_call):
            if entity.is_on:
                tasks.append(entity.async_turn_off())
            else:
                tasks.append(entity.async_turn_on())

        if tasks:
            yield from asyncio.wait(tasks, loop=hass.loop)

    @asyncio.coroutine
    def reload_service_handler(service_call):
        """Remove all automations and load new ones from config."""
        conf = yield from component.async_prepare_reload()
        if conf is None:
            return
        yield from _async_process_config(hass, conf, component)

    hass.services.async_register(
        DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
        descriptions.get(SERVICE_TRIGGER), schema=TRIGGER_SERVICE_SCHEMA)

    hass.services.async_register(
        DOMAIN, SERVICE_RELOAD, reload_service_handler,
        descriptions.get(SERVICE_RELOAD), schema=RELOAD_SERVICE_SCHEMA)

    hass.services.async_register(
        DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
        descriptions.get(SERVICE_TOGGLE), schema=SERVICE_SCHEMA)

    for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
        hass.services.async_register(
            DOMAIN, service, turn_onoff_service_handler,
            descriptions.get(service), schema=SERVICE_SCHEMA)

    return True

class AutomationEntity(ToggleEntity):
    """Entity to show status of entity."""

    def __init__(self, name, async_attach_triggers, cond_func, async_action,
                 hidden):
        """Initialize an automation entity."""
        self._name = name
        self._async_attach_triggers = async_attach_triggers
        self._async_detach_triggers = None
        self._cond_func = cond_func
        self._async_action = async_action
        self._enabled = False
        self._last_triggered = None
        self._hidden = hidden

    @property
    def name(self):
        """Name of the automation."""
        return self._name

    @property
    def should_poll(self):
        """No polling needed for automation entities."""
        return False

    @property
    def state_attributes(self):
        """Return the entity state attributes."""
        return {
            ATTR_LAST_TRIGGERED: self._last_triggered
        }

    @property
    def hidden(self) -> bool:
        """Return True if the automation entity should be hidden from UIs."""
        return self._hidden

    @property
    def is_on(self) -> bool:
        """Return True if entity is on."""
        return self._enabled

    @asyncio.coroutine
    def async_turn_on(self, **kwargs) -> None:
        """Turn the entity on and update the state."""
        if self._enabled:
            return

        yield from self.async_enable()
        yield from self.async_update_ha_state()

    @asyncio.coroutine
    def async_turn_off(self, **kwargs) -> None:
        """Turn the entity off."""
        if not self._enabled:
            return

        self._async_detach_triggers()
        self._async_detach_triggers = None
        self._enabled = False
        yield from self.async_update_ha_state()

    @asyncio.coroutine
    def async_trigger(self, variables, skip_condition=False):
        """Trigger automation.

        This method is a coroutine.
        """
        if skip_condition or self._cond_func(variables):
            yield from self._async_action(self.entity_id, variables)
            self._last_triggered = utcnow()
            yield from self.async_update_ha_state()

    @asyncio.coroutine
    def async_remove(self):
        """Remove automation from HASS."""
        yield from self.async_turn_off()
        yield from super().async_remove()

    @asyncio.coroutine
    def async_enable(self):
        """Enable this automation entity.

        This method is a coroutine.
        """
        if self._enabled:
            return

        self._async_detach_triggers = yield from self._async_attach_triggers(
            self.async_trigger)
        self._enabled = True

@asyncio.coroutine
def _async_process_config(hass, config, component):
    """Process config and add automations.

    This method is a coroutine.
    """
    entities = []
    tasks = []

    for config_key in extract_domain_configs(config, DOMAIN):
        conf = config[config_key]

        for list_no, config_block in enumerate(conf):
            name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
                                                                  list_no)

            hidden = config_block[CONF_HIDE_ENTITY]

            action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
                                       name)

            if CONF_CONDITION in config_block:
                cond_func = _async_process_if(hass, config, config_block)

                if cond_func is None:
                    continue
            else:
                def cond_func(variables):
                    """Condition will always pass."""
                    return True

            async_attach_triggers = partial(
                _async_process_trigger, hass, config,
                config_block.get(CONF_TRIGGER, []), name)
            entity = AutomationEntity(name, async_attach_triggers, cond_func,
                                      action, hidden)
            if config_block[CONF_INITIAL_STATE]:
                tasks.append(entity.async_enable())
            entities.append(entity)

    if tasks:
        yield from asyncio.wait(tasks, loop=hass.loop)
    if entities:
        yield from component.async_add_entities(entities)

    return len(entities) > 0

def _async_get_action(hass, config, name):
    """Return an action based on a configuration."""
    script_obj = script.Script(hass, config, name)

    @asyncio.coroutine
    def action(entity_id, variables):
        """Action to be executed."""
        _LOGGER.info('Executing %s', name)
        logbook.async_log_entry(
            hass, name, 'has been triggered', DOMAIN, entity_id)
        yield from script_obj.async_run(variables)

    return action

def _async_process_if(hass, config, p_config):
    """Process if checks."""
    if_configs = p_config.get(CONF_CONDITION)

    checks = []
    for if_config in if_configs:
        try:
            checks.append(condition.async_from_config(if_config, False))
        except HomeAssistantError as ex:
            _LOGGER.warning('Invalid condition: %s', ex)
            return None

    def if_action(variables=None):
        """AND all conditions."""
        return all(check(hass, variables) for check in checks)

    return if_action

@asyncio.coroutine
def _async_process_trigger(hass, config, trigger_configs, name, action):
    """Setup the triggers.

    This method is a coroutine.
    """
    removes = []

    for conf in trigger_configs:
        platform = yield from async_prepare_setup_platform(
            hass, config, DOMAIN, conf.get(CONF_PLATFORM))

        if platform is None:
            return None

        remove = platform.async_trigger(hass, conf, action)

        if not remove:
            _LOGGER.error("Error setting up trigger %s", name)
            continue

        _LOGGER.info("Initialized trigger %s", name)
        removes.append(remove)

    if not removes:
        return None

    def remove_triggers():
        """Remove attached triggers."""
        for remove in removes:
            remove()

    return remove_triggers
<|endoftext|>


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

import tensorflow as tf
import reader_utils as utils
import os
from functools import partial

def resize_axis(tensor, axis, new_size, fill_value=0):
	"""Truncates or pads a tensor to new_size on on a given axis.

	Truncate or extend tensor such that tensor.shape[axis] == new_size. If the
	size increases, the padding will be performed at the end, using fill_value.

	Args:
		tensor: The tensor to be resized.
		axis: An integer representing the dimension to be sliced.
		new_size: An integer or 0d tensor representing the new value for
			tensor.shape[axis].
		fill_value: Value to use to fill any new entries in the tensor. Will be cast
			to the type of tensor.

	Returns:
		The resized tensor.
	"""
	tensor = tf.convert_to_tensor(tensor)
	shape = tf.unstack(tf.shape(tensor))

	pad_shape = shape[:]
	pad_shape[axis] = tf.maximum(0, new_size - shape[axis])

	shape[axis] = tf.minimum(shape[axis], new_size)
	shape = tf.stack(shape)

	resized = tf.concat([
			tf.slice(tensor, tf.zeros_like(shape), shape),
			tf.fill(tf.stack(pad_shape), tf.cast(fill_value, tensor.dtype))
	], axis)

	# Update shape.
	new_shape = tensor.get_shape().as_list()  # A copy is being made.
	new_shape[axis] = new_size
	resized.set_shape(new_shape)
	return resized

class YT8MFrameFeatureDataset():
	"""Reads TFRecords of SequenceExamples.

	The TFRecords must contain SequenceExamples with the sparse in64 'labels'
	context feature and a fixed length byte-quantized feature vector, obtained
	from the features in 'feature_names'. The quantized features will be mapped
	back into a range between min_quantized_value and max_quantized_value.
	"""

	def __init__(  # pylint: disable=dangerous-default-value
			self,
			num_classes=3862,
			feature_sizes=[1024, 128],
			feature_names=["rgb", "audio"],
			max_frames=300):
		"""Construct a YT8MFrameFeatureDataset.

		Args:
			num_classes: a positive integer for the number of classes.
			feature_sizes: positive integer(s) for the feature dimensions as a list.
			feature_names: the feature name(s) in the tensorflow record as a list.
			max_frames: the maximum number of frames to process.
		"""

		assert len(feature_names) == len(feature_sizes), (
				"length of feature_names (={}) != length of feature_sizes (={})".format(
						len(feature_names), len(feature_sizes)))

		self.num_classes = num_classes
		self.feature_sizes = feature_sizes
		self.feature_names = feature_names
		self.max_frames = max_frames

	def get_video_matrix(self, features, feature_size, max_frames, max_quantized_value, min_quantized_value):
		"""Decodes features from an input string and quantizes it.

		Args:
			features: raw feature values
			feature_size: length of each frame feature vector
			max_frames: number of frames (rows) in the output feature_matrix
			max_quantized_value: the maximum of the quantized value.
			min_quantized_value: the minimum of the quantized value.

		Returns:
			feature_matrix: matrix of all frame-features
			num_frames: number of frames in the sequence
		"""
		decoded_features = tf.reshape(
				tf.cast(tf.io.decode_raw(features, tf.uint8), tf.float32),
				[-1, feature_size])

		num_frames = tf.minimum(tf.shape(decoded_features)[0], max_frames)
		feature_matrix = utils.Dequantize(decoded_features, max_quantized_value,
																			min_quantized_value)
		feature_matrix = resize_axis(feature_matrix, 0, max_frames)
		return feature_matrix, num_frames

	def get_dataset(self, data_dir, batch_size, type="train", max_quantized_value=2, min_quantized_value=-2):
		"""Returns TFRecordDataset after it has been parsed.

		Args:
			data_dir: directory of the TFRecords
		Returns:
			dataset: TFRecordDataset of the input training data
		"""
		files = tf.io.matching_files(os.path.join(data_dir, "train0000.tfrecord"))#'%s*' % type))

		files_dataset = tf.data.Dataset.from_tensor_slices(files)
		files_dataset = files_dataset.batch(tf.cast(tf.shape(files)[0], tf.int64))

		dataset = files_dataset.interleave(lambda files: tf.data.TFRecordDataset(files, num_parallel_reads=tf.data.experimental.AUTOTUNE))
		#dataset = dataset.shuffle(buffer_size=5*batch_size)

		parser = partial(self._parse_fn, max_quantized_value=max_quantized_value, min_quantized_value=min_quantized_value)
		dataset = dataset.map(parser, num_parallel_calls=tf.data.experimental.AUTOTUNE)

		dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(tf.data.experimental.AUTOTUNE)

		return dataset

	def _parse_fn(self, serialized_example, max_quantized_value=2, min_quantized_value=-2):
		"""Parse single Serialized Example from the TFRecords."""

		# Read/parse frame/segment-level labels.
		context_features = {
				"id": tf.io.FixedLenFeature([], tf.string),
				"labels": tf.io.VarLenFeature(tf.int64)
		}

		sequence_features = {
				feature_name: tf.io.FixedLenSequenceFeature([], dtype=tf.string)
				for feature_name in self.feature_names
		}
		contexts, features = tf.io.parse_single_sequence_example(
				serialized_example,
				context_features=context_features,
				sequence_features=sequence_features)

		# loads (potentially) different types of features and concatenates them
		num_features = len(self.feature_names)
		assert num_features > 0, "No feature selected: feature_names is empty!"

		assert len(self.feature_names) == len(self.feature_sizes), (
				"length of feature_names (={}) != length of feature_sizes (={})".format(
						len(self.feature_names), len(self.feature_sizes)))

		num_frames = -1  # the number of frames in the video
		feature_matrices = [None] * num_features  # an array of different features
		for feature_index in range(num_features):
			feature_matrix, num_frames_in_this_feature = self.get_video_matrix(
					features[self.feature_names[feature_index]],
					self.feature_sizes[feature_index], self.max_frames,
					max_quantized_value, min_quantized_value)
			if num_frames == -1:
				num_frames = num_frames_in_this_feature

			feature_matrices[feature_index] = feature_matrix

		# cap the number of frames at self.max_frames
		num_frames = tf.minimum(num_frames, self.max_frames)

		# concatenate different features
		video_matrix = tf.concat(feature_matrices, 1)

		#Select num_samples frames.
		num_frames = tf.expand_dims(num_frames, 0)

		# Process video-level labels.
		label_indices = contexts["labels"].values
		sparse_labels = tf.sparse.SparseTensor(
				tf.expand_dims(label_indices, axis=-1),
				tf.ones_like(contexts["labels"].values, dtype=tf.bool),
				(self.num_classes,))
		labels = tf.sparse.to_dense(sparse_labels, default_value=False, validate_indices=False)
		# convert to batch format.
		batch_video_ids = tf.expand_dims(contexts["id"], 0)

		batch_video_matrix = video_matrix
		batch_labels = labels

		feature_dim = len(batch_video_matrix.get_shape()) - 1

		batch_video_matrix = tf.nn.l2_normalize(batch_video_matrix, feature_dim)

		return (batch_video_matrix, batch_labels)

#Ways to implement random frames.
#1. Simply add it in input and loop over dataset myself. Only issue is with validation but that can be fixed, maybe?
#2. Do it in loading. I can simply do a random uniform between 0 and 1 (not included) and then multiply it by the num_frames, then round down.
<commit_msg>Fixing Training<commit_after># Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Provides readers configured for different datasets."""

import tensorflow as tf
import reader_utils as utils
import os
from functools import partial

def resize_axis(tensor, axis, new_size, fill_value=0):
	"""Truncates or pads a tensor to new_size on on a given axis.

	Truncate or extend tensor such that tensor.shape[axis] == new_size. If the
	size increases, the padding will be performed at the end, using fill_value.

	Args:
		tensor: The tensor to be resized.
		axis: An integer representing the dimension to be sliced.
		new_size: An integer or 0d tensor representing the new value for
			tensor.shape[axis].
		fill_value: Value to use to fill any new entries in the tensor. Will be cast
			to the type of tensor.

	Returns:
		The resized tensor.
	"""
	tensor = tf.convert_to_tensor(tensor)
	shape = tf.unstack(tf.shape(tensor))

	pad_shape = shape[:]
	pad_shape[axis] = tf.maximum(0, new_size - shape[axis])

	shape[axis] = tf.minimum(shape[axis], new_size)
	shape = tf.stack(shape)

	resized = tf.concat([
			tf.slice(tensor, tf.zeros_like(shape), shape),
			tf.fill(tf.stack(pad_shape), tf.cast(fill_value, tensor.dtype))
	], axis)

	# Update shape.
	new_shape = tensor.get_shape().as_list()  # A copy is being made.
	new_shape[axis] = new_size
	resized.set_shape(new_shape)
	return resized

class YT8MFrameFeatureDataset():
	"""Reads TFRecords of SequenceExamples.

	The TFRecords must contain SequenceExamples with the sparse in64 'labels'
	context feature and a fixed length byte-quantized feature vector, obtained
	from the features in 'feature_names'. The quantized features will be mapped
	back into a range between min_quantized_value and max_quantized_value.
	"""

	def __init__(  # pylint: disable=dangerous-default-value
			self,
			num_classes=3862,
			feature_sizes=[1024, 128],
			feature_names=["rgb", "audio"],
			max_frames=300):
		"""Construct a YT8MFrameFeatureDataset.

		Args:
			num_classes: a positive integer for the number of classes.
			feature_sizes: positive integer(s) for the feature dimensions as a list.
			feature_names: the feature name(s) in the tensorflow record as a list.
			max_frames: the maximum number of frames to process.
		"""

		assert len(feature_names) == len(feature_sizes), (
				"length of feature_names (={}) != length of feature_sizes (={})".format(
						len(feature_names), len(feature_sizes)))

		self.num_classes = num_classes
		self.feature_sizes = feature_sizes
		self.feature_names = feature_names
		self.max_frames = max_frames

	def get_video_matrix(self, features, feature_size, max_frames, max_quantized_value, min_quantized_value):
		"""Decodes features from an input string and quantizes it.

		Args:
			features: raw feature values
			feature_size: length of each frame feature vector
			max_frames: number of frames (rows) in the output feature_matrix
			max_quantized_value: the maximum of the quantized value.
			min_quantized_value: the minimum of the quantized value.

		Returns:
			feature_matrix: matrix of all frame-features
			num_frames: number of frames in the sequence
		"""
		decoded_features = tf.reshape(
				tf.cast(tf.io.decode_raw(features, tf.uint8), tf.float32),
				[-1, feature_size])

		num_frames = tf.minimum(tf.shape(decoded_features)[0], max_frames)
		feature_matrix = utils.Dequantize(decoded_features, max_quantized_value,
																			min_quantized_value)
		feature_matrix = resize_axis(feature_matrix, 0, max_frames)
		return feature_matrix, num_frames

	def get_dataset(self, data_dir, batch_size, type="train", max_quantized_value=2, min_quantized_value=-2):
		"""Returns TFRecordDataset after it has been parsed.

		Args:
			data_dir: directory of the TFRecords
		Returns:
			dataset: TFRecordDataset of the input training data
		"""
		files = tf.io.matching_files(os.path.join(data_dir, '*%s*', "0000"))#'%s*' % type))

		files_dataset = tf.data.Dataset.from_tensor_slices(files)
		files_dataset = files_dataset.batch(tf.cast(tf.shape(files)[0], tf.int64))

		dataset = files_dataset.interleave(lambda files: tf.data.TFRecordDataset(files, num_parallel_reads=tf.data.experimental.AUTOTUNE))
		#dataset = dataset.shuffle(buffer_size=5*batch_size)

		parser = partial(self._parse_fn, max_quantized_value=max_quantized_value, min_quantized_value=min_quantized_value)
		dataset = dataset.map(parser, num_parallel_calls=tf.data.experimental.AUTOTUNE)

		dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(tf.data.experimental.AUTOTUNE)

		return dataset

	def _parse_fn(self, serialized_example, max_quantized_value=2, min_quantized_value=-2):
		"""Parse single Serialized Example from the TFRecords."""

		# Read/parse frame/segment-level labels.
		context_features = {
				"id": tf.io.FixedLenFeature([], tf.string),
				"labels": tf.io.VarLenFeature(tf.int64)
		}

		sequence_features = {
				feature_name: tf.io.FixedLenSequenceFeature([], dtype=tf.string)
				for feature_name in self.feature_names
		}
		contexts, features = tf.io.parse_single_sequence_example(
				serialized_example,
				context_features=context_features,
				sequence_features=sequence_features)

		# loads (potentially) different types of features and concatenates them
		num_features = len(self.feature_names)
		assert num_features > 0, "No feature selected: feature_names is empty!"

		assert len(self.feature_names) == len(self.feature_sizes), (
				"length of feature_names (={}) != length of feature_sizes (={})".format(
						len(self.feature_names), len(self.feature_sizes)))

		num_frames = -1  # the number of frames in the video
		feature_matrices = [None] * num_features  # an array of different features
		for feature_index in range(num_features):
			feature_matrix, num_frames_in_this_feature = self.get_video_matrix(
					features[self.feature_names[feature_index]],
					self.feature_sizes[feature_index], self.max_frames,
					max_quantized_value, min_quantized_value)
			if num_frames == -1:
				num_frames = num_frames_in_this_feature

			feature_matrices[feature_index] = feature_matrix

		# cap the number of frames at self.max_frames
		num_frames = tf.minimum(num_frames, self.max_frames)

		# concatenate different features
		video_matrix = tf.concat(feature_matrices, 1)

		#Select num_samples frames.
		num_frames = tf.expand_dims(num_frames, 0)

		# Process video-level labels.
		label_indices = contexts["labels"].values
		sparse_labels = tf.sparse.SparseTensor(
				tf.expand_dims(label_indices, axis=-1),
				tf.ones_like(contexts["labels"].values, dtype=tf.bool),
				(self.num_classes,))
		labels = tf.sparse.to_dense(sparse_labels, default_value=False, validate_indices=False)
		# convert to batch format.
		batch_video_ids = tf.expand_dims(contexts["id"], 0)

		batch_video_matrix = video_matrix
		batch_labels = labels

		feature_dim = len(batch_video_matrix.get_shape()) - 1

		batch_video_matrix = tf.nn.l2_normalize(batch_video_matrix, feature_dim)

		return (batch_video_matrix, batch_labels)

#Ways to implement random frames.
#1. Simply add it in input and loop over dataset myself. Only issue is with validation but that can be fixed, maybe?
#2. Do it in loading. I can simply do a random uniform between 0 and 1 (not included) and then multiply it by the num_frames, then round down.
<|endoftext|>


<commit_before>import string
import md5
from sys import argv

def MDPrint(str):
    outstr = ''
    for i in str:
        o = ord(i)
        outstr = (outstr
                  + string.hexdigits[(o >> 4) & 0xF]
                  + string.hexdigits[o & 0xF])
    print outstr,

from time import time

def makestr(start, end):
    result = ''
    for i in range(start, end + 1):
        result = result + chr(i)

    return result

def MDTimeTrial():
    TEST_BLOCK_SIZE = 1000
    TEST_BLOCKS = 10000

    TEST_BYTES = TEST_BLOCK_SIZE * TEST_BLOCKS

    # initialize test data, need temporary string filler

    filsiz = 1 << 8
    filler = makestr(0, filsiz-1)
    data = filler * (TEST_BLOCK_SIZE / filsiz);
    data = data + filler[:(TEST_BLOCK_SIZE % filsiz)]

    del filsiz, filler

    # start timer
    print 'MD5 time trial. Processing', TEST_BYTES, 'characters...'
    t1 = time()

    mdContext = md5.new()

    for i in range(TEST_BLOCKS):
        mdContext.update(data)

    str = mdContext.digest()
    t2 = time()

    MDPrint(str)
    print 'is digest of test input.'
    print 'Seconds to process test input:', t2 - t1
    print 'Characters processed per second:', TEST_BYTES / (t2 - t1)

def MDString(str):
    MDPrint(md5.new(str).digest())
    print '"' + str + '"'

def MDFile(filename):
    f = open(filename, 'rb');
    mdContext = md5.new()

    while 1:
        data = f.read(1024)
        if not data:
            break
        mdContext.update(data)

    MDPrint(mdContext.digest())
    print filename

import sys

def MDFilter():
    mdContext = md5.new()

    while 1:
        data = sys.stdin.read(16)
        if not data:
            break
        mdContext.update(data)

    MDPrint(mdContext.digest())
    print

def MDTestSuite():
    print 'MD5 test suite results:'
    MDString('')
    MDString('a')
    MDString('abc')
    MDString('message digest')
    MDString(makestr(ord('a'), ord('z')))
    MDString(makestr(ord('A'), ord('Z'))
              + makestr(ord('a'), ord('z'))
              + makestr(ord('0'), ord('9')))
    MDString((makestr(ord('1'), ord('9')) + '0') * 8)

    # Contents of file foo are "abc"
    MDFile('foo')

# I don't wanna use getopt(), since I want to use the same i/f...
def main():
    if len(argv) == 1:
        MDFilter()
    for arg in argv[1:]:
        if arg[:2] == '-s':
            MDString(arg[2:])
        elif arg == '-t':
            MDTimeTrial()
        elif arg == '-x':
            MDTestSuite()
        else:
            MDFile(arg)

main()
<commit_msg>#687648 from Robert Schuppenies: use classic division.  From me: remove two stray semicolons<commit_after>import string
import md5
from sys import argv

def MDPrint(str):
    outstr = ''
    for i in str:
        o = ord(i)
        outstr = (outstr
                  + string.hexdigits[(o >> 4) & 0xF]
                  + string.hexdigits[o & 0xF])
    print outstr,

from time import time

def makestr(start, end):
    result = ''
    for i in range(start, end + 1):
        result = result + chr(i)

    return result

def MDTimeTrial():
    TEST_BLOCK_SIZE = 1000
    TEST_BLOCKS = 10000

    TEST_BYTES = TEST_BLOCK_SIZE * TEST_BLOCKS

    # initialize test data, need temporary string filler

    filsiz = 1 << 8
    filler = makestr(0, filsiz-1)
    data = filler * (TEST_BLOCK_SIZE // filsiz)
    data = data + filler[:(TEST_BLOCK_SIZE % filsiz)]

    del filsiz, filler

    # start timer
    print 'MD5 time trial. Processing', TEST_BYTES, 'characters...'
    t1 = time()

    mdContext = md5.new()

    for i in range(TEST_BLOCKS):
        mdContext.update(data)

    str = mdContext.digest()
    t2 = time()

    MDPrint(str)
    print 'is digest of test input.'
    print 'Seconds to process test input:', t2 - t1
    print 'Characters processed per second:', TEST_BYTES / (t2 - t1)

def MDString(str):
    MDPrint(md5.new(str).digest())
    print '"' + str + '"'

def MDFile(filename):
    f = open(filename, 'rb')
    mdContext = md5.new()

    while 1:
        data = f.read(1024)
        if not data:
            break
        mdContext.update(data)

    MDPrint(mdContext.digest())
    print filename

import sys

def MDFilter():
    mdContext = md5.new()

    while 1:
        data = sys.stdin.read(16)
        if not data:
            break
        mdContext.update(data)

    MDPrint(mdContext.digest())
    print

def MDTestSuite():
    print 'MD5 test suite results:'
    MDString('')
    MDString('a')
    MDString('abc')
    MDString('message digest')
    MDString(makestr(ord('a'), ord('z')))
    MDString(makestr(ord('A'), ord('Z'))
              + makestr(ord('a'), ord('z'))
              + makestr(ord('0'), ord('9')))
    MDString((makestr(ord('1'), ord('9')) + '0') * 8)

    # Contents of file foo are "abc"
    MDFile('foo')

# I don't wanna use getopt(), since I want to use the same i/f...
def main():
    if len(argv) == 1:
        MDFilter()
    for arg in argv[1:]:
        if arg[:2] == '-s':
            MDString(arg[2:])
        elif arg == '-t':
            MDTimeTrial()
        elif arg == '-x':
            MDTestSuite()
        else:
            MDFile(arg)

main()
<|endoftext|>


<commit_before>
import discord
from discord.ext import commands, menus, tasks

import asyncio
import datetime
from typing import Optional

from parsedatetime import Calendar, VERSION_CONTEXT_STYLE

from utilities import checks
from utilities.menu import Menu

def setup(bot):
	bot.add_cog(Reminders(bot))

class Reminders(commands.Cog):

	def __init__(self, bot):
		self.bot = bot

		self.calendar = Calendar(version = VERSION_CONTEXT_STYLE)
		# Add mo as valid abbreviation for month
		self.calendar.ptc.units["months"].append("mo")

		# Add menu as subcommand of reminder and reminder list commands
		self.reminder_command.add_command(self.menu_command)
		self.list_reminders.add_command(self.menu_command)

		self.menus = []

		self.current_timer = None
		self.new_reminder = asyncio.Event()
		self.restarting_timer = False
		self.timer.start().set_name("Reminders")

	def cog_unload(self):
		for menu in self.menus:
			menu.stop()
		self.timer.cancel()

	async def initialize_database(self):
		await self.bot.connect_to_database()
		await self.bot.db.execute("CREATE SCHEMA IF NOT EXISTS reminders")
		await self.bot.db.execute(
			"""
			CREATE TABLE IF NOT EXISTS reminders.reminders (
				id				INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
				user_id			BIGINT,
				channel_id		BIGINT,
				message_id		BIGINT,
				created_time	TIMESTAMPTZ DEFAULT NOW(),
				remind_time		TIMESTAMPTZ,
				reminder		TEXT,
				reminded		BOOL DEFAULT FALSE,
				cancelled 		BOOL DEFAULT FALSE,
				failed			BOOL DEFAULT FALSE
			)
			"""
		)

	async def cog_check(self, ctx):
		return await checks.not_forbidden().predicate(ctx)

	# TODO: Allow setting timezone (+ for time command as well)

	@commands.group(name = "reminder", aliases = ["remind", "reminders", "timer", "timers"],
					invoke_without_command = True, case_insensitive = True)
	async def reminder_command(self, ctx, *, reminder: Optional[commands.clean_content]):
		'''
		See and set reminders
		Times are in UTC
		'''
		# Invoke list subcommand for reminders and timers invocations
		if ctx.invoked_with in ("reminders", "timers"):
			offset = 0
			count = 10
			try:
				inputs = reminder.split()
				offset = int(inputs[0])
				count = int(inputs[1])
			except (AttributeError, IndexError, ValueError):
				pass
			return await ctx.invoke(self.list_reminders, offset, count)
		elif not reminder:
			raise commands.BadArgument("Time not specified")
		# Clean reminder input
		for prefix in ("me about ", "me to ", "me "):
			if reminder.startswith(prefix):
				reminder = reminder[len(prefix):]
		reminder = reminder.replace("from now", "")
		# Parse reminder
		now = datetime.datetime.now(datetime.timezone.utc)
		if not (matches := self.calendar.nlp(reminder, sourceTime = now)):
			raise commands.BadArgument("Time not specified")
		parsed_datetime, context, start_pos, end_pos, matched_text = matches[0]
		if not context.hasTime:
			parsed_datetime = parsed_datetime.replace(hour = now.hour, minute = now.minute,
														second = now.second, microsecond = now.microsecond)
		parsed_datetime = parsed_datetime.replace(tzinfo = datetime.timezone.utc)
		if parsed_datetime < now:
			raise commands.BadArgument("Time is in the past")
		# Respond
		reminder = reminder[:start_pos] + reminder[end_pos + 1:]
		reminder = reminder.strip()
		response = await ctx.embed_reply(fields = (("Reminder", reminder or ctx.bot.ZWS),),
											footer_text = f"Set for {parsed_datetime.isoformat(timespec = 'seconds').replace('+00:00', 'Z')}",
											timestamp = parsed_datetime)
		# Insert into database
		created_time = ctx.message.created_at.replace(tzinfo = datetime.timezone.utc)
		await self.bot.db.execute(
			"""
			INSERT INTO reminders.reminders (user_id, channel_id, message_id, created_time, remind_time, reminder)
			VALUES ($1, $2, $3, $4, $5, $6)
			""",
			ctx.author.id, ctx.channel.id, response.id, created_time, parsed_datetime, reminder
		)
		# Update timer
		if self.current_timer and parsed_datetime < self.current_timer["remind_time"]:
			self.restarting_timer = True
			self.timer.restart()
			self.timer.get_task().set_name("Reminders")
		else:
			self.new_reminder.set()

	@reminder_command.command(aliases = ["delete", "remove"])
	async def cancel(self, ctx, reminder_id: int):
		'''Cancel a reminder'''
		cancelled = await ctx.bot.db.fetchrow(
			"""
			UPDATE reminders.reminders
			SET cancelled = TRUE
			WHERE id = $1 AND user_id = $2 AND reminded = FALSE AND failed = FALSE
			RETURNING *
			""",
			reminder_id, ctx.author.id
		)
		if not cancelled:
			return await ctx.embed_reply(":no_entry: Error: Unable to find and cancel reminder")
		if self.current_timer and self.current_timer["id"] == reminder_id:
			self.restarting_timer = True
			self.timer.restart()
			self.timer.get_task().set_name("Reminders")
		await ctx.embed_reply(fields = (("Cancelled Reminder", cancelled["reminder"] or ctx.bot.ZWS),),
								footer_text = f"Set for {cancelled['remind_time'].isoformat(timespec = 'seconds').replace('+00:00', 'Z')}",
								timestamp = cancelled["remind_time"])

	@reminder_command.group(name = "list", invoke_without_command = True, case_insensitive = True)
	async def list_reminders(self, ctx, offset: Optional[int] = 0, count: Optional[int] = 10):
		'''
		List reminders
		Max count is 10
		'''
		records = await ctx.bot.db.fetch(
			"""
			SELECT id, channel_id, message_id, remind_time, reminder
			FROM reminders.reminders
			WHERE user_id = $1 AND reminded = FALSE AND cancelled = FALSE AND failed = FALSE
			ORDER BY remind_time
			LIMIT $2 OFFSET $3
			""",
			ctx.author.id, min(count, 10), offset
		)
		fields = []
		for record in records:
			value = record["reminder"] or "Reminder"
			if channel := ctx.bot.get_channel(record["channel_id"]):
				try:
					message = await channel.fetch_message(record["message_id"])
					value = f"[{value}]({message.jump_url})"
				except discord.NotFound:
					pass
				value += f"\nIn {getattr(channel, 'mention', 'DMs')}"
			# TODO: Attempt to fetch channel?
			value += f"\nAt {record['remind_time'].isoformat(timespec = 'seconds').replace('+00:00', 'Z')}"
			fields.append((f"ID: {record['id']}", value))
		await ctx.embed_reply(title = "Reminders", fields = fields)

	@commands.command(name = "menu", aliases = ['m', "menus", 'r', "reaction", "reactions"])
	async def menu_command(self, ctx, per_page: Optional[int] = 10):
		'''
		Reminders menu
		Max per_page is 10
		'''
		records = await ctx.bot.db.fetch(
			"""
			SELECT id, channel_id, message_id, remind_time, reminder
			FROM reminders.reminders
			WHERE user_id = $1 AND reminded = FALSE AND cancelled = FALSE AND failed = FALSE
			ORDER BY remind_time
			""",
			ctx.author.id
		)
		menu = RemindersMenu(records, per_page = min(per_page, 10))
		self.menus.append(menu)
		await menu.start(ctx, wait = True)
		self.menus.remove(menu)

	# TODO: clear subcommand

	# R/PT0S
	@tasks.loop()
	async def timer(self):
		record = await self.bot.db.fetchrow(
			"""
			SELECT * FROM reminders.reminders
			WHERE reminded = FALSE AND cancelled = FALSE AND failed = FALSE
			ORDER BY remind_time
			LIMIT 1
			"""
		)
		if not record:
			self.new_reminder.clear()
			return await self.new_reminder.wait()
		self.current_timer = record
		await discord.utils.sleep_until(record["remind_time"])
		if not (channel := self.bot.get_channel(record["channel_id"])):
			# TODO: Attempt to fetch channel?
			return await self.bot.db.execute("UPDATE reminders.reminders SET failed = TRUE WHERE id = $1", record["id"])
		user = self.bot.get_user(record["user_id"]) or self.bot.fetch_user(record["user_id"])
		# TODO: Handle user not found?
		embed = discord.Embed(color = self.bot.bot_color)
		try:
			message = await channel.fetch_message(record["message_id"])
			embed.description = f"[{record['reminder'] or 'Reminder'}]({message.jump_url})"
		except discord.NotFound:
			embed.description = record["reminder"] or "Reminder"
		embed.set_footer(text = "Reminder set")
		embed.timestamp = record["created_time"]
		try:
			await channel.send(user.mention, embed = embed)
		except discord.Forbidden:
			# TODO: Attempt to send without embed
			# TODO: Fall back to DM
			await self.bot.db.execute("UPDATE reminders.reminders SET failed = TRUE WHERE id = $1", record["id"])
		else:
			await self.bot.db.execute("UPDATE reminders.reminders SET reminded = TRUE WHERE id = $1", record["id"])

	@timer.before_loop
	async def before_timer(self):
		await self.initialize_database()
		await self.bot.wait_until_ready()

	@timer.after_loop
	async def after_timer(self):
		if self.restarting_timer:
			self.restarting_timer = False
		else:
			print(f"{self.bot.console_message_prefix}Reminders task cancelled @ {datetime.datetime.now().isoformat()}")

class RemindersMenu(Menu, menus.MenuPages):

	def __init__(self, records, per_page):
		super().__init__(RemindersSource(records, per_page), timeout = None, clear_reactions_after = True, check_embeds = True)

	async def send_initial_message(self, ctx, channel):
		message = await super().send_initial_message(ctx, channel)
		await ctx.bot.attempt_delete_message(ctx.message)
		return message

class RemindersSource(menus.ListPageSource):

	def __init__(self, records, per_page):
		super().__init__(records, per_page = per_page)

	async def format_page(self, menu, records):
		embed = discord.Embed(title = "Reminders", color = menu.bot.bot_color)
		embed.set_author(name = menu.ctx.author.display_name, icon_url = menu.ctx.author.avatar_url)
		if self.per_page == 1:
			records = [records]
		for record in records:
			value = record["reminder"] or "Reminder"
			if channel := menu.bot.get_channel(record["channel_id"]):
				try:
					message = await channel.fetch_message(record["message_id"])
					value = f"[{value}]({message.jump_url})"
				except discord.NotFound:
					pass
				value += f"\nIn {getattr(channel, 'mention', 'DMs')}"
			# TODO: Attempt to fetch channel?
			value += f"\nAt {record['remind_time'].isoformat(timespec = 'seconds').replace('+00:00', 'Z')}"
			embed.add_field(name = f"ID: {record['id']}", value = value)
		offset = menu.current_page * self.per_page
		start = offset + 1
		end = min(offset + self.per_page, len(self.entries))
		if start < end:
			embed.set_footer(text = f"Reminders {start} - {end} of {len(self.entries)}")
		elif start == end:
			embed.set_footer(text = f"Reminder {start} of {len(self.entries)}")
		else:
			return embed.set_footer(text = f"In response to: {menu.ctx.message.clean_content}")
		return {"content": f"In response to: `{menu.ctx.message.clean_content}`", "embed": embed}

<commit_msg>[Discord] Use error emoji Bot attribute in reminder cancel command<commit_after>
import discord
from discord.ext import commands, menus, tasks

import asyncio
import datetime
from typing import Optional

from parsedatetime import Calendar, VERSION_CONTEXT_STYLE

from utilities import checks
from utilities.menu import Menu

def setup(bot):
	bot.add_cog(Reminders(bot))

class Reminders(commands.Cog):

	def __init__(self, bot):
		self.bot = bot

		self.calendar = Calendar(version = VERSION_CONTEXT_STYLE)
		# Add mo as valid abbreviation for month
		self.calendar.ptc.units["months"].append("mo")

		# Add menu as subcommand of reminder and reminder list commands
		self.reminder_command.add_command(self.menu_command)
		self.list_reminders.add_command(self.menu_command)

		self.menus = []

		self.current_timer = None
		self.new_reminder = asyncio.Event()
		self.restarting_timer = False
		self.timer.start().set_name("Reminders")

	def cog_unload(self):
		for menu in self.menus:
			menu.stop()
		self.timer.cancel()

	async def initialize_database(self):
		await self.bot.connect_to_database()
		await self.bot.db.execute("CREATE SCHEMA IF NOT EXISTS reminders")
		await self.bot.db.execute(
			"""
			CREATE TABLE IF NOT EXISTS reminders.reminders (
				id				INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
				user_id			BIGINT,
				channel_id		BIGINT,
				message_id		BIGINT,
				created_time	TIMESTAMPTZ DEFAULT NOW(),
				remind_time		TIMESTAMPTZ,
				reminder		TEXT,
				reminded		BOOL DEFAULT FALSE,
				cancelled 		BOOL DEFAULT FALSE,
				failed			BOOL DEFAULT FALSE
			)
			"""
		)

	async def cog_check(self, ctx):
		return await checks.not_forbidden().predicate(ctx)

	# TODO: Allow setting timezone (+ for time command as well)

	@commands.group(name = "reminder", aliases = ["remind", "reminders", "timer", "timers"],
					invoke_without_command = True, case_insensitive = True)
	async def reminder_command(self, ctx, *, reminder: Optional[commands.clean_content]):
		'''
		See and set reminders
		Times are in UTC
		'''
		# Invoke list subcommand for reminders and timers invocations
		if ctx.invoked_with in ("reminders", "timers"):
			offset = 0
			count = 10
			try:
				inputs = reminder.split()
				offset = int(inputs[0])
				count = int(inputs[1])
			except (AttributeError, IndexError, ValueError):
				pass
			return await ctx.invoke(self.list_reminders, offset, count)
		elif not reminder:
			raise commands.BadArgument("Time not specified")
		# Clean reminder input
		for prefix in ("me about ", "me to ", "me "):
			if reminder.startswith(prefix):
				reminder = reminder[len(prefix):]
		reminder = reminder.replace("from now", "")
		# Parse reminder
		now = datetime.datetime.now(datetime.timezone.utc)
		if not (matches := self.calendar.nlp(reminder, sourceTime = now)):
			raise commands.BadArgument("Time not specified")
		parsed_datetime, context, start_pos, end_pos, matched_text = matches[0]
		if not context.hasTime:
			parsed_datetime = parsed_datetime.replace(hour = now.hour, minute = now.minute,
														second = now.second, microsecond = now.microsecond)
		parsed_datetime = parsed_datetime.replace(tzinfo = datetime.timezone.utc)
		if parsed_datetime < now:
			raise commands.BadArgument("Time is in the past")
		# Respond
		reminder = reminder[:start_pos] + reminder[end_pos + 1:]
		reminder = reminder.strip()
		response = await ctx.embed_reply(fields = (("Reminder", reminder or ctx.bot.ZWS),),
											footer_text = f"Set for {parsed_datetime.isoformat(timespec = 'seconds').replace('+00:00', 'Z')}",
											timestamp = parsed_datetime)
		# Insert into database
		created_time = ctx.message.created_at.replace(tzinfo = datetime.timezone.utc)
		await self.bot.db.execute(
			"""
			INSERT INTO reminders.reminders (user_id, channel_id, message_id, created_time, remind_time, reminder)
			VALUES ($1, $2, $3, $4, $5, $6)
			""",
			ctx.author.id, ctx.channel.id, response.id, created_time, parsed_datetime, reminder
		)
		# Update timer
		if self.current_timer and parsed_datetime < self.current_timer["remind_time"]:
			self.restarting_timer = True
			self.timer.restart()
			self.timer.get_task().set_name("Reminders")
		else:
			self.new_reminder.set()

	@reminder_command.command(aliases = ["delete", "remove"])
	async def cancel(self, ctx, reminder_id: int):
		'''Cancel a reminder'''
		cancelled = await ctx.bot.db.fetchrow(
			"""
			UPDATE reminders.reminders
			SET cancelled = TRUE
			WHERE id = $1 AND user_id = $2 AND reminded = FALSE AND failed = FALSE
			RETURNING *
			""",
			reminder_id, ctx.author.id
		)
		if not cancelled:
			return await ctx.embed_reply(f"{ctx.bot.error_emoji} Error: Unable to find and cancel reminder")
		if self.current_timer and self.current_timer["id"] == reminder_id:
			self.restarting_timer = True
			self.timer.restart()
			self.timer.get_task().set_name("Reminders")
		await ctx.embed_reply(fields = (("Cancelled Reminder", cancelled["reminder"] or ctx.bot.ZWS),),
								footer_text = f"Set for {cancelled['remind_time'].isoformat(timespec = 'seconds').replace('+00:00', 'Z')}",
								timestamp = cancelled["remind_time"])

	@reminder_command.group(name = "list", invoke_without_command = True, case_insensitive = True)
	async def list_reminders(self, ctx, offset: Optional[int] = 0, count: Optional[int] = 10):
		'''
		List reminders
		Max count is 10
		'''
		records = await ctx.bot.db.fetch(
			"""
			SELECT id, channel_id, message_id, remind_time, reminder
			FROM reminders.reminders
			WHERE user_id = $1 AND reminded = FALSE AND cancelled = FALSE AND failed = FALSE
			ORDER BY remind_time
			LIMIT $2 OFFSET $3
			""",
			ctx.author.id, min(count, 10), offset
		)
		fields = []
		for record in records:
			value = record["reminder"] or "Reminder"
			if channel := ctx.bot.get_channel(record["channel_id"]):
				try:
					message = await channel.fetch_message(record["message_id"])
					value = f"[{value}]({message.jump_url})"
				except discord.NotFound:
					pass
				value += f"\nIn {getattr(channel, 'mention', 'DMs')}"
			# TODO: Attempt to fetch channel?
			value += f"\nAt {record['remind_time'].isoformat(timespec = 'seconds').replace('+00:00', 'Z')}"
			fields.append((f"ID: {record['id']}", value))
		await ctx.embed_reply(title = "Reminders", fields = fields)

	@commands.command(name = "menu", aliases = ['m', "menus", 'r', "reaction", "reactions"])
	async def menu_command(self, ctx, per_page: Optional[int] = 10):
		'''
		Reminders menu
		Max per_page is 10
		'''
		records = await ctx.bot.db.fetch(
			"""
			SELECT id, channel_id, message_id, remind_time, reminder
			FROM reminders.reminders
			WHERE user_id = $1 AND reminded = FALSE AND cancelled = FALSE AND failed = FALSE
			ORDER BY remind_time
			""",
			ctx.author.id
		)
		menu = RemindersMenu(records, per_page = min(per_page, 10))
		self.menus.append(menu)
		await menu.start(ctx, wait = True)
		self.menus.remove(menu)

	# TODO: clear subcommand

	# R/PT0S
	@tasks.loop()
	async def timer(self):
		record = await self.bot.db.fetchrow(
			"""
			SELECT * FROM reminders.reminders
			WHERE reminded = FALSE AND cancelled = FALSE AND failed = FALSE
			ORDER BY remind_time
			LIMIT 1
			"""
		)
		if not record:
			self.new_reminder.clear()
			return await self.new_reminder.wait()
		self.current_timer = record
		await discord.utils.sleep_until(record["remind_time"])
		if not (channel := self.bot.get_channel(record["channel_id"])):
			# TODO: Attempt to fetch channel?
			return await self.bot.db.execute("UPDATE reminders.reminders SET failed = TRUE WHERE id = $1", record["id"])
		user = self.bot.get_user(record["user_id"]) or self.bot.fetch_user(record["user_id"])
		# TODO: Handle user not found?
		embed = discord.Embed(color = self.bot.bot_color)
		try:
			message = await channel.fetch_message(record["message_id"])
			embed.description = f"[{record['reminder'] or 'Reminder'}]({message.jump_url})"
		except discord.NotFound:
			embed.description = record["reminder"] or "Reminder"
		embed.set_footer(text = "Reminder set")
		embed.timestamp = record["created_time"]
		try:
			await channel.send(user.mention, embed = embed)
		except discord.Forbidden:
			# TODO: Attempt to send without embed
			# TODO: Fall back to DM
			await self.bot.db.execute("UPDATE reminders.reminders SET failed = TRUE WHERE id = $1", record["id"])
		else:
			await self.bot.db.execute("UPDATE reminders.reminders SET reminded = TRUE WHERE id = $1", record["id"])

	@timer.before_loop
	async def before_timer(self):
		await self.initialize_database()
		await self.bot.wait_until_ready()

	@timer.after_loop
	async def after_timer(self):
		if self.restarting_timer:
			self.restarting_timer = False
		else:
			print(f"{self.bot.console_message_prefix}Reminders task cancelled @ {datetime.datetime.now().isoformat()}")

class RemindersMenu(Menu, menus.MenuPages):

	def __init__(self, records, per_page):
		super().__init__(RemindersSource(records, per_page), timeout = None, clear_reactions_after = True, check_embeds = True)

	async def send_initial_message(self, ctx, channel):
		message = await super().send_initial_message(ctx, channel)
		await ctx.bot.attempt_delete_message(ctx.message)
		return message

class RemindersSource(menus.ListPageSource):

	def __init__(self, records, per_page):
		super().__init__(records, per_page = per_page)

	async def format_page(self, menu, records):
		embed = discord.Embed(title = "Reminders", color = menu.bot.bot_color)
		embed.set_author(name = menu.ctx.author.display_name, icon_url = menu.ctx.author.avatar_url)
		if self.per_page == 1:
			records = [records]
		for record in records:
			value = record["reminder"] or "Reminder"
			if channel := menu.bot.get_channel(record["channel_id"]):
				try:
					message = await channel.fetch_message(record["message_id"])
					value = f"[{value}]({message.jump_url})"
				except discord.NotFound:
					pass
				value += f"\nIn {getattr(channel, 'mention', 'DMs')}"
			# TODO: Attempt to fetch channel?
			value += f"\nAt {record['remind_time'].isoformat(timespec = 'seconds').replace('+00:00', 'Z')}"
			embed.add_field(name = f"ID: {record['id']}", value = value)
		offset = menu.current_page * self.per_page
		start = offset + 1
		end = min(offset + self.per_page, len(self.entries))
		if start < end:
			embed.set_footer(text = f"Reminders {start} - {end} of {len(self.entries)}")
		elif start == end:
			embed.set_footer(text = f"Reminder {start} of {len(self.entries)}")
		else:
			return embed.set_footer(text = f"In response to: {menu.ctx.message.clean_content}")
		return {"content": f"In response to: `{menu.ctx.message.clean_content}`", "embed": embed}

<|endoftext|>