def subscribe(self, feed: "Feed"): """ Subscribe user to given feed :param feed: feed to subscribe :return: """ if self.feed_subs >= MAX_SUBSCRIPTIONS_FREE: return False # if user is banned from feed he can't subscribe if Ban.by_user_and_feed(self, feed) is not None: return False # save subscription db.table("feeds_users").insert(user_id=self.id, feed_id=feed.id) key = "subs:{}".format(self.id) ids = cache.get(key) or [] ids.append(feed.id) cache.set(key, ids) self.incr("feed_subs", 1) self.update_with_cache() # TODO DO IN QUEUE feed.incr("subscribers_count", 1) return True
def by_slug(cls, slug: str) -> Optional["Feed"]: """ Get feed by slug :param username: slug :return: """ cache_key = "fslug:{}".format(slug) # check feed slug cache in_cache = cache.get(cache_key, raw=True) uid = int(in_cache) if in_cache else None # return user on success if uid is not None: return Feed.by_id(uid) # try to load user from DB on failure feed = Feed.where("slug", slug).first() # cache the result if feed is not None: cache.set(cache_key, feed.id, raw=True) feed.write_to_cache() return feed
def update(self, comments: ["Comment"]): """ Update sorted comments in cache This should be called on votes (maybe not all of them) and on new comments :param link_id: link id :param comment: comment """ for comment in comments: cache_key = self._cache_key(comment.parent_id) lock_key = self._lock_key(comment.parent_id) # update comment under read - write - modify lock with Lock(cache.conn, lock_key): # maybe check against the comment tree to see if it is missing or it just is not initialized yet comments = ( cache.get(cache_key) or [] ) # so maybe load comments instead of [] # update comment for i in range(len(comments)): if comments[i][0] == comment.id: comments[i] = [ comment.id, confidence(comment.ups, comment.downs), ] break else: # add comment comments.append( [comment.id, confidence(comment.ups, comment.downs)] ) # sort and save comments = sorted(comments, key=lambda x: x[1:], reverse=True) cache.set(cache_key, comments)
def by_username(cls, username: str) -> Optional["User"]: """ Get user by username :param username: username :return: """ cache_key = "uname:{}".format(username) # check username cache in_cache = cache.get(cache_key, raw=True) uid = int(in_cache) if in_cache else None # return user on success if uid is not None: return User.by_id(uid) # try to load user from DB on failure u = User.where("username", username).first() # cache the result if u is not None: cache.set(cache_key, u.id, raw=True) u.write_to_cache() return u
def write_to_cache(self): """ Write self to cache What should and what shouldn't be written can be modified by __hidden__ attribute on class (more in documentation of orator) """ # save token to redis for limited time cache.set(self._cache_key, self.serialize())
def subscribed_feed_ids(self) -> List[str]: """ Get users subscribed feed ids :return: list of ids """ key = "subs:{}".format(self.id) ids = cache.get(key) if ids is None: ids = [feed.id for feed in self.feeds] cache.set(key, ids) return ids
def add(self, comments: ["Comment"]): """ Adds comment to comment tree for given link :param link: link :param comment: comment """ with Lock(cache.conn, self._lock_key): tree = self.load_tree() for comment in comments: tree.setdefault(comment.parent_id, []).append(comment.id) cache.set(self._cache_key, tree)
def remove(self, comments: ["Comment"]): """ Remove comments from comment tree :param comments: comments """ with Lock(cache.conn, self._lock_key): tree = self.load_tree() for comment in comments: tree[comment.parent_id] = [ id for id in tree[comment.parent_id] if id != comment.id ] cache.set(self._cache_key, tree)
def unsubscribe(self, feed: "Feed"): db.table("feeds_users").where("user_id", "=", self.id).where("feed_id", "=", feed.id).delete() self.decr("feed_subs", 1) # TODO DO IN QUEUE feed.decr("subscribers_count", 1) key = "subs:{}".format(self.id) ids = cache.get(key) if ids is not None: ids = [id for id in ids if id != feed.id] cache.set(key, ids) return True
def by_slug(cls, slug) -> Optional["Link"]: """ Get link by slug :param slug: slug :return: maybe link """ cache_key = "lslug:{}".format(slug) id = cache.get(cache_key) if id is None: link = Link.where("slug", slug).first() if link is not None: id = link.id cache.set(cache_key, id) return Link.by_id(id) if id else None
def create(self): """ Creates email verification which expires after given time and sends email to user to verify his email """ # create token self.token = token_urlsafe(16) # save token to redis for limited time cache.set(self._cache_key, self.user.id, ttl=PASSWORD_RESET_EXPIRE, raw=True) # send email with verification link msg = reset_email(self.user, self._url) q.enqueue(JOB_send_mail, msg, result_ttl=0)
def login(self, remember_me: bool = False): """ Login the user Generate access token which gets saved in session, info about user is then saved to redis :param remember_me: """ token = DisposableToken.get() self._accessor_cache["session_token"] = token.id session_key = "us:{}".format(self.session_token) cache.set(session_key, self.id, ttl=0 if remember_me else 60 * 60 * 2, raw=True) login_user(self, remember=remember_me) LinkVote.by_user_and_vote_type(self.id, UPVOTE) LinkVote.by_user_and_vote_type(self.id, DOWNVOTE) CommentVote.by_user_and_vote_type(self.id, UPVOTE) CommentVote.by_user_and_vote_type(self.id, DOWNVOTE)
def load_tree(self) -> dict: """ Load the tree :return: tree """ tree = cache.get(self._cache_key) if not tree: comments = ( Comment.where("link_id", self.link_id).select("parent_id", "id").get() or [] ) tree = {} for comment in comments: tree.setdefault(comment.parent_id, []).append(comment.id) cache.set(self._cache_key, tree) self._tree = tree return tree
def build_tree(self): """ Build sorted tree of comments for given comment_id If comment_id is None, tree for whole link is build :param comment_id: comment_id for comment, None for link :return: (comment_id, [sorted subtrees]) """ # load all comments that will be needed comment_ids = self._tree.ids() comments = ( {comment.id: comment for comment in Comment.by_ids(comment_ids)} if comment_ids else {} ) comments.setdefault(None) ids = self._tree.keys() children_tuples = cache.mget([self._cache_key(id) for id in ids]) if ids else [] for idx, parent_id in enumerate(ids): # fill in missing children if children_tuples[idx] is None: children = ( Comment.where("parent_id", parent_id) .where("link_id", self._link_id) .get() ) tuples = [[x.id, confidence(x.ups, x.downs)] for x in children] children_tuples[idx] = sorted(tuples, key=lambda x: x[1:], reverse=True) cache.set(self._cache_key(parent_id), children_tuples[idx]) builder = dict(zip(ids, children_tuples)) # subtree builder def build_subtree(parent): return [ comments[parent], [build_subtree(children_id) for children_id, _ in builder[parent]] if parent in builder else [], ] return build_subtree(None)
def get_by_feed_id(cls, feed_id: int, sort: str) -> ["Link"]: """ Get links by feed id and sort :param feed_id: :param sort: :return: """ cache_key = "fs:{}.{}".format(feed_id, sort) r = cache.get(cache_key) if r is not None: return r q = Link.where("feed_id", feed_id).order_by_raw(sorts[sort]) # cache needs array of objects, not a orator collection res = [f for f in q.limit(1000).get()] # TODO this is stupid, cache only ids? cache.set(cache_key, res) return res
def write_to_cache(self): """ Write self to cache """ cache.set(self.id, "y")
def rules(self, value): rules = markdown(value, safe_mode="escape") cache.set(self.rules_cache_key, rules) self.set_raw_attribute("rules", value)
def rules_html(self): rules = cache.get(self.rules_cache_key) if rules is None: rules = markdown(self.rules) if self.rules else "" cache.set(self.rules_cache_key, rules) return rules
def _save(self): """ Save data to cache """ assert self._fetched cache.set(self._cache_key, self._data)
def create(self): cache.set(self._cache_key, {})