class UserRedis(walrus.Model): __database__ = WALRUS_DB _id = walrus.AutoIncrementField(walrus.IntegerField) username = walrus.TextField() password = walrus.TextField() age = walrus.IntegerField() place = walrus.TextField() dob = walrus.DateTimeField()
class Player(walrus.Model): @staticmethod def new(session_id=uuid.uuid4(), user="******", wall=0) -> 'Player': player = Player.create( id=uuid.uuid4(), room=NULL_UUID, username=user, score=0, ) player.paddle['pos'] = .5 player.paddle['wall'] = wall player.save() return Player.load(player.id) def set_room(self, wall: int, room: uuid.UUID) -> 'Player': self.room = room self.paddle['wall'] = wall self.save() return Player.load(self.id) def updatePaddle(self, newPos): self.paddle['pos'] = newPos self.save() def to_json(self): ''' Recursively convert fields to json-friendly output. Return a dict with the following schema: { id: "uuid", score: int, paddle: { wall: int, pos: float, }, } ''' return dict( id=str(self.id), score=self.score, paddle=dict( wall=as_int(self.paddle['wall']), pos=asFloat(self.paddle['pos']), ), ) __database__ = walrus_conn id = walrus.UUIDField(primary_key=True, index=True) room = walrus.UUIDField() username = walrus.TextField() score = walrus.IntegerField() paddle = walrus.HashField()
class Task(walrus.Model): __database__ = redis_db id = walrus.TextField(primary_key=True) args = walrus.TextField() stdout = walrus.TextField() stderr = walrus.TextField() returncode = walrus.IntegerField() state = walrus.TextField()
class Room(walrus.Model): @staticmethod def new() -> 'Room': room = Room.create( id=uuid.uuid4(), lastUpdate=time(), ) return room def add_player(self, player) -> Player: ''' Add a player to the room by id or instance and set their room field. Return the updated Player instance loaded from redis. player -- Type uuid.UUID or Player ''' if not (isinstance(player, Player) or isinstance(player, uuid.UUID)): raise TypeError("Parameter must be of type" "multipong.model.Player or uuid.UUID," "not {}".format(type(player))) if isinstance(player, Player): player = player.id self.players.add(player) self.wall += 2 self.save() added = Player.load(player) added.set_room(self.wall - 2, self.id) self.__update_ball_count() # Return the updated player model added = Player.load(player) return added def __update_ball_count(self): ''' Check the ball count for the room to ensure that it is equal to the number of players currently playing.''' numPlayers = len(self.players) numBalls = len(self.balls) if numPlayers > numBalls: self.add_ball() elif numPlayers < numBalls: self.pop_last_ball() def remove_player(self, player) -> Player: ''' Remove player from room but do not delete instance. We also check to make sure that the room is balanced for the number of current players. ''' if not (isinstance(player, Player) or isinstance(player, uuid.UUID)): raise TypeError("Parameter must be of type" "multipong.model.Player or uuid.UUID," "not {}".format(type(player))) if isinstance(player, Player): player = player.id self.players.remove(player) self.wall -= 2 self.save() self.__update_ball_count() return Player.load(player).set_room(wall=-1, room=NULL_UUID) def add_ball(self) -> Ball: ball = Ball.new() self.balls.append(ball.id) return ball def delete_ball(self, uid: uuid.UUID): del self.balls[uid] Ball.load(uid).delete() def pop_last_ball(self): self.balls.popright() def ball_at(self, index: int) -> Ball: ball_id = uuid.UUID(self.balls[index].decode('utf-8')) return Ball.load(ball_id) def moveBalls(self): elapsedTime = time() - self.lastUpdate self.lastUpdate = time() self.save() for ball in self.balls: ball = ball.decode('utf-8') Ball.load(ball).move(elapsedTime) def to_json(self) -> dict: ''' Recursively convert fields to json-friendly output. Return a dict with the following schema: { id: "uuid", balls: [<see Ball.to_json()>], players: [<see Player.to_json()>] } ''' as_uuid = lambda obj: uuid.UUID(obj.decode('utf-8')) return dict( id=str(self.id), balls=list(map( lambda ball: Ball.load(ball).to_json(), map(as_uuid, self.balls))), players=list(map( lambda player: Player.load(player).to_json(), map(as_uuid, self.players))), ) __database__ = walrus_conn id = walrus.UUIDField(primary_key=True, index=True) balls = walrus.ListField() players = walrus.SetField() wall = walrus.IntegerField(default=0) lastUpdate = walrus.FloatField(default=0)
class UserDetail(walrus.Model): """ A walrus data model to store some user data to Redis to be synced to Postgres asynchronously. """ __database__ = redis_connection __namespace__ = 'redash.user.details' user_id = walrus.IntegerField(index=True) updated_at = UTCDateTimeField(index=True, default=utcnow) @classmethod def update(cls, user_id): """ Update the user details hash using the given redis pipeline, user id, optional redis id and optional user details. The fields uid, rid and updated (timestamp) are enforced and can't be overwritten. """ # try getting the user detail with the given user ID # or create one if it doesn't exist yet (e.g. when key was purged) try: user_detail = cls.get(cls.user_id == user_id) # update the timestamp with the current time user_detail.updated_at = utcnow() # save to Redis user_detail.save() except ValueError: user_detail = cls.create( user_id=user_id, updated_at=utcnow(), ) return user_detail @classmethod def sync(cls, chunksize=1000): """ Syncs user details to Postgres (to the JSON field User.details). """ to_sync = {} try: for user_detail in cls.all(): to_sync[user_detail.user_id] = user_detail user_ids = list(to_sync.keys()) if not user_ids: return logger.info('syncing users: %s', ', '.join([str(uid) for uid in user_ids])) # get all SQLA users that need to be updated users = User.query.filter(User.id.in_(user_ids)) for i, user in enumerate(users): update = to_sync[user.id] user.active_at = update.updated_at # flush changes to the database after a certain # number of items and extend the list of keys to # stop sync in case of exceptions if i % chunksize == 0: db.session.flush() db.session.commit() except DBAPIError: # reset list of keys to stop sync pass finally: user_ids = [str(user_id) for user_id in to_sync.keys()] if user_ids: logger.info('Deleting temporary user details for users %s', ', '.join(user_ids)) delete_query = [ UserDetail.user_id == str(user_id) for user_id in user_ids ] UserDetail.query_delete(reduce(or_, delete_query))