Skip to content

Instantly share code, notes, and snippets.

@FND
Created February 24, 2012 11:23
Show Gist options
  • Save FND/1900281 to your computer and use it in GitHub Desktop.
Save FND/1900281 to your computer and use it in GitHub Desktop.
simple Redis-based microblogging application
#!/usr/bin/env python
"""
StatusQ client
"""
from __future__ import absolute_import, division, with_statement
import sys
from time import time
from redis import StrictRedis
from pretty import date as pretty_date
# TODO: read from config
HOST = "localhost"
PORT = 6379
REDIS_DB = 0
COMMANDS = {}
def main(args):
args = [unicode(arg, "utf-8") for arg in args]
cmd = args[1]
params = args[2:]
db = StrictRedis(host=HOST, port=PORT, db=REDIS_DB)
# TODO: use pipeline transactions
COMMANDS[cmd](db, *params)
return True
def command(func):
"""
decorator which adds the respective method to the COMMANDS dictionary
"""
COMMANDS[func.__name__] = func
return func
@command
def read(db, username, _type="overlords"): # TODO: rename `_type` argument
# TODO: authenticate user
if _type == "overlords":
key = "users:%s:%s" % (username, "stream")
elif _type == "all":
key = "posts:%s" % username
else:
raise TypeError, "invalid stream type"
posts = db.lrange(key, 0, 10)
if len(posts) == 0:
print "no posts here yet"
for pid in posts:
msg = db.get("posts:%s" % pid)
author = db.get("posts:%s:author" % pid)
timestamp = float(db.get("posts:%s:timestamp" % pid))
print "[%s] (%s) %s" % (author, pretty_date(int(timestamp)), msg)
@command
def post(db, username, msg):
# TODO: authenticate user, validate message
pid = db.incr("posts:enum")
db.set("posts:%s" % pid, msg)
db.set("posts:%s:author" % pid, username)
db.set("posts:%s:timestamp" % pid, time())
db.lpush("posts", pid)
db.lpush("posts:%s" % username, pid)
for minion in db.smembers("users:%s:minions" % username):
db.lpush("users:%s:stream" % minion, pid)
# TODO: mentions
#for user in extract_usernames($txt)
# LPUSH users:$user:pings $pid
@command
def watch(db, username, *overlords):
# TODO: authenticate user
for overlord in overlords:
db.sadd("users:%s:overlords" % username, overlord)
db.sadd("users:%s:minions" % overlord, username)
@command
def unwatch(db, username, *overlords):
# TODO: authenticate user
for overlord in overlords:
db.sadd("users:%s:overlords" % username, overlord)
db.sadd("users:%s:minions" % overlord, username)
@command
def create_user(db, username, password):
# TODO: validate input
uid = db.incr("users:enum")
db.set("users:%s:uid" % username, uid) # XXX: unnecessary?
db.set("users:%s" % username, password) # TODO: hash password
db.sadd("users", username)
if __name__ == "__main__":
status = not main(sys.argv)
sys.exit(status)
# INT = integer
# STR = string
# () = set
# [] = list
# == USERS ==
#
# users = () # usernames -- XXX: use list?
# users
# enum = INT
# $username = STR # password -- XXX: more efficient to use $uid?
# uid = INT
# password = INT
# minions = () # usernames -- TODO: rename ("observers", "acolytes")? -- XXX: use list?
# overlords = () # usernames -- TODO: rename? -- XXX: use list?
# stream = [] # post IDs (reverse chronological order)
# pings = [] # post IDs (reverse chronological order) -- TODO: rename?
#
# == POSTS ==
#
# posts = [] # post IDs (reverse chronological order)
# posts
# enum = INT
# $username = [] # post IDs (reverse chronological order) -- XXX: key collision hazard
# $pid = STR
# author = INT
# timestamp = INT # epoch
create_user: $username $password
$uid = INCR users:enum
SET users:$username:uid $uid # XXX: unnecessary?
SET users:$username hash($password)
SADD users $username
create_post: $username $msg
$pid = INCR posts:enum
SET posts:$pid $msg
SET posts:$pid:author $username
SET posts:$pid:timestamp now()
LPUSH posts $pid
$minions = SMEMBERS users:$username:minions
for $minion in $minions
LPUSH users:$minion:stream $pid
for $user in extract_usernames($msg)
LPUSH users:$user:pings $pid
watch: $subject $object
SADD users:$subject:overlords $object
SADD users:$object:minions $subject
# stream is not changed retroactively (for performance reasons - otherwise
# we'd have to traverse the entire stream, retrieving each post's timestamp
# to inject $object's posts - though immutable history might also be
# considered desirable)
unwatch: $subject $object
SREM users:$subject:overlords $object
SREM users:$object:minions $subject
# stream is not changed retroactively (for performance reasons - otherwise
# we'd have to traverse the entire stream, retrieving each post's author to
# discard $object's posts - though immutable history might also be
# considered desirable)
.PHONY: client server terminate reset clean
client:
./redis-cli
server: terminate
./redis-server > ./redis.log & echo $$! > ./.pid
terminate:
kill -TERM `cat .pid` || true
rm .pid || true
reset: terminate clean
sleep 1
rm dump.rdb || true
clean:
rm redis.log || true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment