class CriticalPower(grumble.Model, Timestamped): cpdef = grumble.ReferenceProperty(sweattrails.config.CriticalPowerInterval) timestamp = grumble.TimeDeltaProperty(verbose_name="Starting on") atdistance = grumble.IntegerProperty(verbose_name="At distance") power = grumble.IntegerProperty(verbose_name="Power") @classmethod def get_best_for(cls, cpdef): for best in CriticalPower.query(cpdef=cpdef).add_sort( "power", False).set_limit(1): return best return None @classmethod def get_progression(cls, cpdef, user): q = cls.query(cpdef=cpdef).add_sort("p.start_time") q.add_parent_join(BikePart, "part") q.add_join(Session, "_parent", "session", "part") q.add_join(sweattrails.config.CriticalPowerInterval, "cpdef", alias="cpi") q.add_filter("session.athlete =", user) q.add_condition( """k.power > COALESCE((SELECT MAX(cp.power) FROM %s cp INNER JOIN %s bp ON (bp._key = cp._parent) INNER JOIN %s sess ON (sess._key = bp._parent) WHERE cp.cpdef = %%s AND sess.start_time < session.start_time), k.power - 1)""" % (cls.modelmanager.tablename, BikePart.modelmanager.tablename, Session.modelmanager.tablename), str(cpdef.key())) q.add_sort("k.power", False) return q
class GeoData(grumble.Model): max_elev = grumble.IntegerProperty( default=-100, verbose_name="Max. Elevation") # In meters min_elev = grumble.IntegerProperty( default=10000, verbose_name="Min. Elevation") # In meters elev_gain = grumble.IntegerProperty( default=0, verbose_name="Elevation Gain") # In meters elev_loss = grumble.IntegerProperty( default=0, verbose_name="Elevation Loss") # In meters bounding_box = grumble.geopt.GeoBoxProperty() def get_session(self): return self.parent().get_session()
class RunPart(IntervalPart): average_power = grumble.IntegerProperty(verbose_name="Average Power", default=0, suffix="W") # W average_watts_per_kg = WattsPerKgProperty(powerproperty="average_power", suffix="W/kg") normalized_power = grumble.IntegerProperty(verbose_name="Normalized Power", suffix="W") # W normalized_watts_per_kg = WattsPerKgProperty( powerproperty="normalized_power", suffix="W/kg") max_power = grumble.IntegerProperty(verbose_name="Maximum Power", default=0, suffix="W") # W max_watts_per_kg = WattsPerKgProperty(powerproperty="max_power", suffix="W/kg") average_cadence = grumble.IntegerProperty(default=0, suffix="strides/min") # rpm max_cadence = grumble.IntegerProperty(default=0, suffix="strides/min") # rpm def reset(self): RunPace.query(ancestor=self).delete() self.average_cadence = 0 self.max_cadence = 0 def reducers(self): ret = [] for cpdef in self.get_activityprofile().get_all_linked_references( sweattrails.config.CriticalPace): if cpdef.distance <= self.get_interval().distance: p = RunPace(parent=self) p.cpdef = cpdef p.distance = cpdef.distance ret.append(RunPaceReducer(p)) maxspeed = None for pzdef in self.get_activityprofile().get_all_linked_references( sweattrails.config.PaceZone): p = TimeInPaceZone(parent=self) p.pzdef = pzdef ret.append(TimeInZoneReducer(p, maxspeed)) maxspeed = pzdef.minSpeed - 1 ret.extend([ Maximize("power", self, "max_power"), AverageOverTime("timestamp", "power", self, "average_power"), NormalizedPowerReducer(self), Maximize("cadence", self, "max_cadence"), AverageOverTime("timestamp", "cadence", self, "average_cadence") ]) return ret
class Person(grumble.Model): name = grumble.TextProperty(required=True, is_label=True, is_key=True, scoped=True) age = grumble.IntegerProperty(default=30, validator=check_age) can_drive = CanDriveProperty()
class HttpAccess(grumble.Model): _flat = True _audit = False timestamp = grumble.DateTimeProperty(auto_now_add=True) remote_addr = grumble.TextProperty() user = grumble.TextProperty() path = grumble.TextProperty() method = grumble.TextProperty() status = grumble.TextProperty() elapsed = grumble.IntegerProperty()
class BikePart(IntervalPart): average_power = grumble.IntegerProperty(verbose_name="Average Power", default=0, suffix="W") # W average_watts_per_kg = WattsPerKgProperty(powerproperty="average_power", suffix="W/kg") normalized_power = grumble.IntegerProperty(verbose_name="Normalized Power", suffix="W") # W normalized_watts_per_kg = WattsPerKgProperty( powerproperty="normalized_power", suffix="W/kg") max_power = grumble.IntegerProperty(verbose_name="Maximum Power", default=0, suffix="W") # W max_watts_per_kg = WattsPerKgProperty(powerproperty="max_power", suffix="W/kg") average_cadence = grumble.IntegerProperty(verbose_name="Average Cadence", default=0, suffix="rpm") # rpm max_cadence = grumble.IntegerProperty(verbose_name="Maximum Cadence", default=0, suffix="rpm") # rpm average_torque = grumble.FloatProperty(verbose_name="Average Torque", default=0.0, suffix="Nm") # Nm max_torque = grumble.FloatProperty(verbose_name="Maximum Torque", default=0.0, suffix="Nm") # Nm vi = VIProperty(verbose_name="VI", default=0.0) intensity_factor = IFProperty(verbose_name="IF", default=0.0) tss = TSSProperty(verbose_name="TSS", default=0.0) def get_ftp(self): if not hasattr(self, "_ftp"): interval = self.parent()() athlete = interval.get_athlete() bikepart = sweattrails.userprofile.BikeProfile.get_userpart( athlete) self._ftp = bikepart.get_ftp( self.get_date()) if bikepart is not None else 0 return self._ftp def get_max_power(self): interval = self.parent()() athlete = interval.get_athlete() bikepart = sweattrails.userprofile.BikeProfile.get_userpart(athlete) return bikepart.get_max_power( self.get_date()) if bikepart is not None else 0 def set_max_power(self, max_power): interval = self.parent()() athlete = interval.get_athlete() bikepart = sweattrails.userprofile.BikeProfile.get_userpart(athlete) if bikepart is not None: bikepart.set_max_power(max_power, self.get_date()) def get_watts_per_kg(self, watts): interval = self.parent()() athlete = interval.get_athlete() bikepart = sweattrails.userprofile.BikeProfile.get_userpart(athlete) return bikepart.get_watts_per_kg( watts, self.get_date()) if bikepart is not None else 0 def reducers(self): ret = [] for cpdef in self.get_activityprofile().get_all_linked_references( sweattrails.config.CriticalPowerInterval): if cpdef.duration <= self.get_interval().duration: cp = CriticalPower(parent=self) cp.cpdef = cpdef cp.put() ret.append(CriticalPowerReducer(cp)) maxpower = None for pzdef in self.get_activityprofile().get_all_linked_references( sweattrails.config.PowerZone): p = TimeInPowerZone(parent=self) p.pzdef = pzdef ret.append(TimeInZoneReducer(p, maxpower)) maxpower = pzdef.minPower - 1 ret.extend([ Maximize("torque", self, "max_torque"), AverageOverTime("timestamp", "torque", self, "average_torque"), Maximize("cadence", self, "max_cadence"), AverageOverTime("timestamp", "cadence", self, "average_cadence"), Maximize("power", self, "max_power"), AverageOverTime("timestamp", "power", self, "average_power"), NormalizedPowerReducer(self) ]) return ret def reset(self): CriticalPower.query(ancestor=self).delete() self.average_power = 0 self.normalized_power = 0 self.max_power = 0 self.average_cadence = 0 self.max_cadence = 0 self.average_torque = 0 self.max_torque = 0
def __call__(self, cls): assert isinstance(cls, grumble.ModelMetaClass) logger.debug("Decorating %s as a process", cls.__name__) cls.add_property("starttime", grumble.DateTimeProperty()) cls.add_property("finishtime", grumble.DateTimeProperty()) cls.add_property("semaphore", grumble.IntegerProperty(default=0)) cls._grudge_process_class = True cls._statuses = {} for (propname, propdef) in cls.__dict__.items(): if isinstance(propdef, Status): propdef.name(propname) cls._statuses[propname] = propdef cls._on_started = [] cls._on_stopped = [] cls._subprocesses = [] cls._parent_process = grumble.Model.for_name( self.parent) if self.parent else None if cls._parent_process: cls._parent_process._subprocesses.append(cls) cls._entrypoint = self.entrypoint cls._exitpoint = self.exitpoint cls._start_semaphore = self.start_semaphore def get_status(cls, s): return cls._statuses.get(s.name() if isinstance(s, Status) else s) cls.get_status = classmethod(get_status) def statusses(cls): return cls._statuses cls.statusses = classmethod(statusses) def on_started(cls, action): cls._on_started.append(action) cls.on_started = classmethod(on_started) def on_stopped(cls, action): cls._on_stopped.append(action) cls.on_stopped = classmethod(on_stopped) def subprocesses(cls): return cls._subprocesses cls.subprocesses = classmethod(subprocesses) def instantiate(cls, parent=None, **kwargs): logger.debug("instantiate %s", cls.__name__) with gripe.db.Tx.begin(): p = cls(parent=parent, **kwargs) p.put() for sub in cls.subprocesses(): subcls = grumble.Model.for_name(sub.__name__) subcls.instantiate(p) return p cls.instantiate = classmethod(instantiate) def start(self): if self.starttime is None: with gripe.db.Tx.begin(): self.semaphore += 1 if self.semaphore < self._start_semaphore: logger.debug( "start semaphore value is %s threshold of %s not yet reached", self.semaphore, self._start_semaphore) self.put() return else: logger.debug("starting instance of %s", self.__class__.__name__) self.starttime = datetime.datetime.now() self.put() for a in self._on_started: _queue.put_action(a, process=self) with gripe.db.Tx.begin(): ep = grumble.Model.for_name( self._entrypoint) if self._entrypoint else None if ep: logger.debug("Entrypoint of %s: %s", self.__class__.__name__, ep.__name__) ep_instance = grumble.Query(ep, False, parent=self).get() assert ep_instance, "No instance of entrypoint class '%s' found" % self._entrypoint logger.debug("Starting entrypoint instance") ep_instance.start() else: logger.debug("No entrypoint") cls.start = start def stop(self): if self.starttime is not None and self.finishtime is None: logger.debug("stop instance of %s", self.__class__.__name__) with gripe.db.Tx.begin(): if self.subprocesses(): for sub in grumble.Query(self.subprocesses(), ancestor=self): sub.stop() self.finishtime = datetime.datetime.now() self.put() for a in self._on_stopped: _queue.put_action(a, process=self) if self._exitpoint: p = self.parent() p.stop() cls.stop = stop def has_status(self, status): status = status.name() if isinstance(status, Status) else status assert status in self._statuses, "Cannot add status %s to process %s" % ( status, self.__class__.__name__) logger.debug("Checking status %s on process %s", status, self.__class__.__name__) added = None with gripe.db.Tx.begin(): for s in AddedStatus.query(parent=self): logger.debug(" -- Status %s found", s.status) if s.status == status: added = s return added is not None cls.has_status = has_status def add_status(self, status): status = status.name() if isinstance(status, Status) else status assert status in self._statuses, "Cannot add status %s to process %s" % ( status, self.__class__.__name__) logger.debug("Adding status %s to process %s", status, self.__class__.__name__) statusdef = self._statuses[status] added = None with gripe.db.Tx.begin(): for s in AddedStatus.query(parent=self): if s.status == status: added = s if added is None: added = AddedStatus(status=status, parent=self) added.put() statusdef.added(self) return added cls.add_status = add_status def remove_status(self, status): status = status.name() if isinstance(status, Status) else status assert status in self._statuses, "Cannot remove status %s from process %s" % ( status, self.__class__.__name__) logger.debug("Removing status %s from process %s", status, self.__class__.__name__) statusdef = self._statuses[status] remove = None with gripe.db.Tx.begin(): for s in AddedStatus.query(parent=self): if s.status == status: remove = s if remove is not None: grumble.delete(remove) statusdef.removed(self) return cls.remove_status = remove_status def resolve_process(self, path): assert path, "Called process.resolve_process with empty path" logger.debug("resolving %s for %s", path, self) proc = self for elem in path.split("/"): logger.debug("Looking for '%s'", elem) if elem == "..": proc = proc().parent() logger.debug("resolved '..' -> %s", proc) elif elem and elem != ".": proc = grumble.Query(elem, parent=proc).get() logger.debug("resolved '%s' -> %s", elem, proc) else: logger.debug("resolve: no-op '%s'", elem) assert proc, "Path %s does not resolve for process %s" % (path, self) return proc() cls.resolve_process = resolve_process def resolve_attribute(self, path, args=None, kwargs=None): assert path, "Called process.resolve_attribute with empty path" logger.debug("resolving attribute %s for %s", path, self) proc = self (procpath, delim, attribname) = path.rpartition(":") call = False if attribname.endswith("()"): call = True attribname = attribname[:-2] if procpath: proc = self.resolve_process(procpath)() assert hasattr(proc, attribname), \ "Resolving %s: Objects of class %s do not have attribute %s" % \ (path, proc.__class__.__name__, attribname) attr = getattr(proc, attribname) if call: assert callable(attr), "Attribute %s of class %s is not callable" % \ (attribname, proc.__class__.__name__) if args is not None: return attr( *args) if kwargs is None else attr(*args, **kwargs) else: return attr() if kwargs is None else attr(**kwargs) else: return attr cls.resolve_attribute = resolve_attribute return cls
class Test(grumble.Model): quux = grumble.StringProperty() froz = grumble.IntegerProperty() icon = grumble.image.ImageProperty()
class Test3(grumble.Model): name = grumble.TextProperty(required=True, is_label=True) value = grumble.IntegerProperty(default=12)