class SensorInitUndoTest(trellis.Component): trellis.attrs(v1=False) @trellis.maintain def a(self): if self.v1: return _Comp() @trellis.maintain def b(self): if self.v1: self.a
class TriagePosition(ItemAddOn): trellis.attrs(pinned_triage_section=None, pinned_position=None) @trellis.compute def _triage_addon(self): return Triage(self._item) @trellis.compute def default_position(self): triage = Triage(self._item) if self._triage_addon.calculated == LATER: future_choices = list(filter_on_time(triage, future=True)) if future_choices: return min(future_choices)[0] # if LATER but no known triage change in the future, use NOW behavior last_past = max(filter_on_time(triage, future=False)) # never return a timestamp less than the item's creation timestamp return max(self._item.created, last_past[0]) @trellis.compute def default_triage_section(self): return self._triage_addon.calculated @trellis.compute def position(self): if self.pinned_position is None: return self.default_position else: return self.pinned_position @trellis.compute def triage_section(self): if self.pinned_triage_section is None: return self.default_triage_section else: return self.pinned_triage_section ### Modifiers ### @trellis.modifier def pin(self): if self.pinned_triage_section is None and self.pinned_position is None: self.pinned_triage_section = self.default_triage_section self.pinned_position = self.default_position @trellis.modifier def clear_pinned(self): self.pinned_position = self.pinned_triage_section = None @trellis.modifier def pin_to_now(self): self.pinned_triage_section = NOW self.pinned_position = nowTimestamp()
class IdleTimer(trellis.Component): trellis.attrs( idle_timeout=20, busy=False, ) idle_for = trellis.maintain( lambda self: self.idle_for.begins_with(not self.busy), initially=NOT_YET) @trellis.maintain # XXX should be perform def alarm(self): if self.idle_for[self.idle_timeout] and EventLoop.running: log.append(5) EventLoop.stop()
class FalseCircularity(trellis.Component): trellis.attrs(v1=False, v2=False) @trellis.maintain def a(self): if self.v1: self.v2 return True @trellis.maintain def b(self): if self.v1: self.v2 = True else: self.a
class InfiniteLoop(trellis.Component): trellis.attrs(v1=False, v2=False) @trellis.maintain def a(self): #print 'A' if self.v1: self.v2 return True @trellis.maintain def b(self): #print 'B' if self.v1: self.a self.v2 = True
class EIM(Extension): uuid = trellis.make(lambda x: uuid4()) well_known_name = trellis.compute(lambda self: self._well_known_name) trellis.attrs( _well_known_name=None, ical_uid=None, ical_extra=None, ) @trellis.modifier def add(self, name=None, **kw): super(EIM, self).add(**kw) if name is None: name = getattr(self._item, 'well_known_name', None) if name is not None: self._well_known_name = name set_item_for_name(name, self.item) set_item_for_uuid(self.uuid, self.item) return self
class TaskExample(trellis.Component): trellis.attrs(start=False, stop=False) def wait_for_start(self): log.append("waiting to start") while not self.start: yield activity.Pause def wait_for_stop(self): while not self.stop: log.append("waiting to stop") yield activity.Pause @activity.task def demo(self): yield self.wait_for_start() log.append("starting") yield self.wait_for_stop() log.append("stopped")
class Reminder(trellis.Component): trellis.attrs( item=None, delta=None, fixed_trigger=None, description="", snooze=None, cleared=False, type='triage', ) @trellis.compute def trigger(self): if self.fixed_trigger is not None: return self.fixed_trigger elif self.delta is not None and self.item is not None: if Event.installed_on(self.item): start = Event(self.item).start if start is not None: return start + self.delta
class Triage(ItemAddOn): trellis.attrs(manual=None, manual_timestamp=None, auto_source=None) @trellis.compute def auto(self): max_timestamp, status = max(filter_on_time(self, future=False)) return status @trellis.compute def calculated(self): if self.manual is not None and self.manual_timestamp is None: return self.manual if self.auto is not None: return self.auto return NOW @trellis.maintain def constraints(self): for cell in (self.manual, self.auto): if cell is not None and int(cell) < 100: raise TriageRangeError(cell)
class Recurrence(Extension): trellis.attrs(frequency=None, byday='', triaged_done_before=None, start_extension=Event, start_extension_cellname='start', recurrence_id_override='base_start') trellis.make.attrs(triaged_recurrence_ids=trellis.Dict, modification_recipes=trellis.Dict, _recurrence_dashboard_entries=trellis.Dict, rdates=trellis.Set, exdates=trellis.Set, _occurrence_cache=dict, _pre_modification_cells=dict) @trellis.compute def start(self): if not self.start_extension.installed_on(self.item): return None else: return self.start_for(self.item) def start_for(self, item): extension = self.start_extension(item) return getattr(extension, self.start_extension_cellname) def build_rrule(self, count=None, until=None): """Return a dateutil rrule based on self. The time-limit for the series can be overridden by setting either count or until. """ kwds = dict(dtstart=self.start, freq=to_dateutil_frequency(self.frequency), byweekday=to_dateutil_byweekday(self.byday), cache=True) if count is not None: kwds['count'] = count elif until is not None: kwds['until'] = until elif self.count is not None: kwds['count'] = self.count else: kwds['until'] = self.until return rrule(**kwds) @trellis.maintain(initially=None) def until(self): """The last possible date for the series.""" if self.count is None: return self.until elif not self.frequency: pass else: rule = self.build_rrule(count=self.count) return rule[-1] @trellis.maintain(initially=None) def count(self): if self.count is None: self.until # make sure the rule depends on until return None elif self.until is None: return None else: rule = self.build_rrule(until=self.until) return rule.count() @trellis.compute def rruleset(self): if self.start is None: return None rrs = rruleset(cache=True) if self.frequency is not None: rrs.rrule(self.build_rrule()) elif not self.rdates: # no rules or rdates, nothing to do return None for dt in self.rdates: rrs.rdate(dt) for dt in self.exdates: rrs.exdate(dt) return rrs def get_occurrence(self, recurrence_id): """Return Occurrence for recurrence_id, cache it for later.""" recurrence_id = to_hashable(recurrence_id) occ = self._occurrence_cache.get(recurrence_id) if not occ: occ = Occurrence(self.item, recurrence_id) self._occurrence_cache[recurrence_id] = occ return occ @trellis.maintain def dashboard_recurrence_ids(self): """The set of recurrence_ids which should have DashboardEntries. Updates self._recurrence_dashboard_entries when re-calculated. """ old_set = self.dashboard_recurrence_ids if old_set is None: old_set = frozenset() if not self in self.item.extensions or not self.start or not self.rruleset: new_set = frozenset() else: now_dt = getNow() past_done = None future_later = None new_set = set() for recurrence_id in self.rruleset: # XXX inefficient, creating an Occurrence for every past recurrence-id, # probably should be optimized by walking backwards from triaged_done_before triage = Triage(self.get_occurrence(recurrence_id)).calculated if triage not in (LATER, DONE): new_set.add(to_hashable(recurrence_id)) else: if triage == DONE and recurrence_id < now_dt: past_done = recurrence_id elif triage == LATER and recurrence_id > now_dt: future_later = recurrence_id break for recurrence_id in past_done, future_later: if recurrence_id is not None: new_set.add(to_hashable(recurrence_id)) new_set.update(self.triaged_recurrence_ids) new_set.update(self.modification_recipes) for recurrence_id in old_set - new_set: del self._recurrence_dashboard_entries[recurrence_id] for recurrence_id in new_set - old_set: entry = DashboardEntry(self.get_occurrence(recurrence_id)) self._recurrence_dashboard_entries[recurrence_id] = entry return new_set @trellis.maintain def _update_dashboard_entries(self): """Keep Item.dashboard_entries in sync with self._recurrence_dashboard_entries.""" recurrence_entries = self._recurrence_dashboard_entries entries = self.item.dashboard_entries # keep the master in entries iff there's no recurrence if len(recurrence_entries) > 0: entries.discard(self.item._default_dashboard_entry) else: entries.add(self.item._default_dashboard_entry) for recurrence_id, entry in recurrence_entries.deleted.iteritems(): entries.discard(entry) for recurrence_id, entry in recurrence_entries.added.iteritems(): entries.add(entry) if recurrence_entries.changed: msg = "a value in recurrence_dashboard_entries changed: %s" raise Exception, msg % recurrence_entries.changed def occurrences_between(self, range_start, range_end): for dt in self.rruleset.between(range_start, range_end, True): yield self.get_occurrence(dt) def pick_dashboard_entry(self, recurrence_id): """Return the DashboardEntry associated with recurrence_id, or None.""" return self._recurrence_dashboard_entries.get( to_hashable(recurrence_id)) def triage_occurrence(self, recurrence_id, timestamp, status): self.triaged_recurrence_ids[to_hashable(recurrence_id)] = (timestamp, status) def clear_occurrence_triage(self, recurrence_id): recurrence_id = to_hashable(recurrence_id) if recurrence_id in self.triaged_recurrence_ids: del self.triaged_recurrence_ids[recurrence_id]
class Occurrence(Item): # override master attributes trellis.attrs(dashboard_entries=None, _default_dashboard_entry=None) inherited_attrs(title=None, ) def inherited_value(self, add_on_instance, name): """Inherit values from modifications, then master.""" cls = None if add_on_instance is self else type(add_on_instance) key = cls, name recipe = self.modification_recipe if recipe and key in recipe.changes: return recipe.changes[key] elif (cls == Recurrence(self.master).start_extension and name == Recurrence(self.master).recurrence_id_override): return self.recurrence_id else: master = self.master if cls is None else cls(self.master) return getattr(master, name) @trellis.compute def created(self): return self.master.created @trellis.compute def _extension_types(self): return frozenset(t for t in self.master._extension_types if t is not Recurrence) @trellis.compute def collections(self): return self.master.collections @property def recurrence_id(self): if isinstance(self.hashable_recurrence_id, datetime): return self.hashable_recurrence_id.replace( tzinfo=TimeZone.floating) master_event = Recurrence(self.master).start_extension(self.master) return datetime.fromtimestamp(self.hashable_recurrence_id, master_event.tzinfo) def __init__(self, master, recurrence_id): self.master = master self.hashable_recurrence_id = to_hashable(recurrence_id) return super(Occurrence, self).__init__() def __repr__(self): return "<Occurrence: %s>" % self.recurrence_id @trellis.modifier def modify(self, add_on=None, name=None, value=None): """Adjust ModificationRecipe such that add_on(self).name == value. If no ModificationRecipe exists, create it. With no arguments, just create a ModificationRecipe for this recurrence-id if one doesn't already exist. """ recipes = Recurrence(self.master).modification_recipes recipe = recipes.get(self.hashable_recurrence_id) if not recipe: recipe = recipes.added.get(self.hashable_recurrence_id) if not recipe: recipe = ModificationRecipe() recipes[self.hashable_recurrence_id] = recipe if name: recipe.make_change(add_on, name, value) @trellis.modifier def remove_change(self, add_on=None, name=None): """Remove a single change from existing ModificationRecipes.""" if self.modification_recipe: self.modification_recipe.remove_change(add_on, name) @trellis.compute def modification_recipe(self): return Recurrence(self.master).modification_recipes.get( self.hashable_recurrence_id) @trellis.modifier def unmodify(self): if self.modification_recipe: del Recurrence( self.master).modification_recipes[self.hashable_recurrence_id]