def assertMembershipIntervalsEqual(self, expected): memberships = session.session.query(Membership).filter_by( user=self.user, group=self.group) got = IntervalSet(closed(m.begins_at, m.ends_at) for m in memberships) assert expected == got, "IntervalSets differ: " \ "expected {0!r}" \ "got {1!r}".format(expected, got)
def test_adding_single_membership(self): begins_at = session.utcnow() ends_at = begins_at + timedelta(hours=1) during = closed(begins_at, ends_at) self.add_membership(during) self.assertMembershipIntervalsEqual(IntervalSet(during))
def assertMembershipIntervalsEqual(self, expected): memberships = session.session.query(Membership).filter_by( user=self.user, group=self.group) got = IntervalSet(m.active_during.closure for m in memberships) assert expected == got, "IntervalSets differ: " \ "expected {!r}" \ "got {!r}".format(expected, got)
def make_member_of(user, group, processor, during=UnboundedInterval): """ Makes a user member of a group in a given interval. If the given interval overlaps with an existing membership, this method will join the overlapping intervals together, so that there will be at most one membership for particular user in particular group at any given point in time. :param User user: the user :param Group group: the group :param User processor: User issuing the addition :param Interval during: """ memberships = session.session.query(Membership).filter( Membership.user == user, Membership.group == group, Membership.active(during)).all() intervals = IntervalSet( closed(m.begins_at, m.ends_at) for m in memberships).union(during) for m in memberships: session.session.delete(m) session.session.add_all( Membership(begins_at=i.begin, ends_at=i.end, user=user, group=group) for i in intervals) message = deferred_gettext(u"Added to group {group} during {during}.") log_user_event(message=message.format(group=group.name, during=during).to_json(), user=user, author=processor)
def remove_member_of(user, group, processor, during=UnboundedInterval): """Remove a user from a group in a given interval. The interval defaults to the unbounded interval, so that the user will be removed from the group at any point in time, **removing all memberships** in this group retroactively. However, a common use case is terminating a membership by setting ``during=closedopen(now, None)``. :param User user: the user :param Group group: the group :param User processor: User issuing the removal :param Interval during: """ memberships = session.session.query(Membership).filter( Membership.user == user, Membership.group == group, Membership.active(during)).all() intervals = IntervalSet( closed(m.begins_at, m.ends_at) for m in memberships).difference(during) for m in memberships: session.session.delete(m) session.session.add_all( Membership(begins_at=i.begin, ends_at=i.end, user=user, group=group) for i in intervals) message = deferred_gettext(u"Removed from group {group} during {during}.") log_user_event(message=message.format(group=group.name, during=during).to_json(), user=user, author=processor)
def make_member_of(user, group, processor, during=UnboundedInterval): """ Makes a user member of a group in a given interval. If the given interval overlaps with an existing membership, this method will join the overlapping intervals together, so that there will be at most one membership for particular user in particular group at any given point in time. :param User user: the user :param Group group: the group :param User processor: User issuing the addition :param Interval during: """ if group.permission_level > processor.permission_level: raise PermissionError("cannot create a membership for a group with a" " higher permission level") memberships: list[Membership] = [ m for m in user.active_memberships(when=during) if m.group == group ] intervals = IntervalSet(m.active_during.closure for m in memberships).union(during) for m in memberships: session.session.delete(m) session.session.flush() session.session.add_all( Membership(active_during=i, user=user, group=group) for i in intervals) message = deferred_gettext("Added to group {group} during {during}.") log_user_event(message=message.format(group=group.name, during=during).to_json(), user=user, author=processor)
def assertIntervalSetOperationEquals(cls, operation, args_and_expected): """ :param callable operation: :param iterable[iterable[IntervalSet], unknown)] args_and_expected: """ cls.assertIntervalSetMethodEquals( operation, ((args, IntervalSet(expected)) for args, expected in args_and_expected))
def test_sort_join(self): self.assertEqual( IntervalSet([ closed(2, 3), closed(2, None), closed(None, 1), closed(1, 3), closed(2, 3), closed(-10, None) ]), IntervalSet([closed(None, None)])) self.assertEqual( IntervalSet([ empty(6), closedopen(1, 2), empty(0), closedopen(2, 3), open(4, 5) ]), IntervalSet([closedopen(1, 3), open(4, 5)]), )
def assertIntervalSetMethodEquals(cls, method, args_and_expected): """ :param callable method: :param iterable[iterable[IntervalSet], unknown)] args_and_expected: """ for args, expected in args_and_expected: args = [IntervalSet(intervals) for intervals in args] got = method(*args) assert got == expected, ( "Evaluating {0}({1}) failed: expected {2}, got {3}".format( method.__name__, ', '.join(map(str, args)), expected, got))
def test_join_overlapping_memberships(self): begins_at1 = session.utcnow() ends_at1 = begins_at1 + timedelta(hours=2) during1 = closed(begins_at1, ends_at1) begins_at2 = begins_at1 + timedelta(hours=1) ends_at2 = begins_at1 + timedelta(hours=3) during2 = closed(begins_at2, ends_at2) self.add_membership(during1) self.add_membership(during2) self.assertMembershipIntervalsEqual(IntervalSet(closed(begins_at1, ends_at2)))
def test_removing_memberships(self): t0 = session.utcnow() t1 = t0 + timedelta(hours=1) t2 = t0 + timedelta(hours=2) t3 = t0 + timedelta(hours=3) t4 = t0 + timedelta(hours=4) t5 = t0 + timedelta(hours=5) self.add_membership(closed(t0, t2)) self.add_membership(closed(t3, t5)) self.remove_membership(closed(t1, t4)) self.assertMembershipIntervalsEqual(IntervalSet( (closed(t0, t1), closed(t4, t5))))
def test_removing_all_memberships(self): begins_at1 = session.utcnow() ends_at1 = begins_at1 + timedelta(hours=1) during1 = closed(begins_at1, ends_at1) begins_at2 = begins_at1 + timedelta(hours=2) ends_at2 = begins_at1 + timedelta(hours=3) during2 = closed(begins_at2, ends_at2) self.add_membership(during1) self.add_membership(during2) self.remove_membership() self.assertMembershipIntervalsEqual(IntervalSet())
def property_intervals(self, name, when=UnboundedInterval): """ Get the set of intervals in which the user was granted a given property :param str name: :param Interval when: :returns: The set of intervals in which the user was granted the property :rtype: IntervalSet """ property_assignments = object_session(self).query( Property.granted, Membership.begins_at, Membership.ends_at).filter( Property.name == name, Property.property_group_id == PropertyGroup.id, PropertyGroup.id == Membership.group_id, Membership.user_id == self.id).all() granted_intervals = IntervalSet( closed(begins_at, ends_at) for granted, begins_at, ends_at in property_assignments if granted) denied_intervals = IntervalSet( closed(begins_at, ends_at) for granted, begins_at, ends_at in property_assignments if not granted) return (granted_intervals - denied_intervals).intersect(when)
def test_type_mangling(self): # TODO one test per assertion and `target` / `base` as fixtures target = IntervalSet([closed(0, 1)]) # Creation assert target == IntervalSet(closed(0, 1)) assert target == IntervalSet([closed(0, 1)]) with pytest.raises(TypeError): IntervalSet(0) # Union base = IntervalSet(()) assert target == base | IntervalSet(closed(0, 1)) assert target == base | closed(0, 1) assert target == base | [closed(0, 1)] # Intersection base = target | closed(1, 2) assert target == base & IntervalSet(openclosed(0, 1)) assert target == base & openclosed(0, 1) assert target == base & [openclosed(0, 1)] # Difference assert target == base - IntervalSet(openclosed(1, 2)) assert target == base - openclosed(1, 2) assert target == base - [openclosed(1, 2)]
def test_type_mangling(self): target = IntervalSet([closed(0, 1)]) # Creation self.assertEqual(target, IntervalSet(closed(0, 1))) self.assertEqual(target, IntervalSet([closed(0, 1)])) self.assertRaises(TypeError, IntervalSet, 0) # Union base = IntervalSet(()) self.assertEqual(target, base | IntervalSet(closed(0, 1))) self.assertEqual(target, base | closed(0, 1)) self.assertEqual(target, base | [closed(0, 1)]) # Intersection base = target | closed(1, 2) self.assertEqual(target, base & IntervalSet(openclosed(0, 1))) self.assertEqual(target, base & openclosed(0, 1)) self.assertEqual(target, base & [openclosed(0, 1)]) # Difference self.assertEqual(target, base - IntervalSet(openclosed(1, 2))) self.assertEqual(target, base - openclosed(1, 2)) self.assertEqual(target, base - [openclosed(1, 2)])
def remove_member_of(user, group, processor, during=UnboundedInterval): """Remove a user from a group in a given interval. The interval defaults to the unbounded interval, so that the user will be removed from the group at any point in time, **removing all memberships** in this group retroactively. However, a common use case is terminating a membership by setting ``during=closedopen(now, None)``. :param User user: the user :param Group group: the group :param User processor: User issuing the removal :param Interval during: """ if group.permission_level > processor.permission_level: raise PermissionError("cannot delete a membership for a group with a" " higher permission level") memberships: list[Membership] = [ m for m in user.active_memberships(when=during) if m.group == group ] intervals = IntervalSet(m.active_during.closure for m in memberships).difference(during) for m in memberships: session.session.delete(m) # flush necessary because we otherwise don't have any control # over the order of deletion vs. addition session.session.flush() session.session.add_all( Membership(active_during=i, user=user, group=group) for i in intervals) message = deferred_gettext("Removed from group {group} during {during}.") log_user_event(message=message.format(group=group.name, during=during).to_json(), user=user, author=processor)
def _to_date_intervals(intervals): """ :param IntervalSet[datetime] intervals: :rtype: IntervalSet[date] """ return IntervalSet(_to_date_interval(i) for i in intervals)
def test_constructor(one: list[Interval], other: list[Interval]): assert IntervalSet(one) == IntervalSet(other)
def test_complement(intervals: list[Interval], expected: IntervalSet): assert IntervalSet(intervals).complement() == IntervalSet(expected)
def test_union(one: list[Interval], other: list[Interval], expected: IntervalSet): assert IntervalSet(one).union(IntervalSet(other)) == IntervalSet(expected)
def test_intersect(one: list[Interval], other: list[Interval], expected: IntervalSet): assert IntervalSet(one).intersect( IntervalSet(other)) == IntervalSet(expected)
def test_difference(one: list[Interval], other: list[Interval], expected: IntervalSet): assert IntervalSet(one).difference( IntervalSet(other)) == IntervalSet(expected)
def test_length(intervals: list[Interval], expected: IntervalSet): assert IntervalSet(intervals).length == expected