Skip to content

Commit f76908a

Browse files
committed
Change to "flat" Message structure
1 parent 34c3d3f commit f76908a

File tree

1 file changed

+156
-39
lines changed

1 file changed

+156
-39
lines changed

couch.py

+156-39
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
from couchdb import client, schema
44
from datetime import datetime
55
import hashlib
6+
import re
67

78
class TimestampDocument(schema.Document):
89
ts = schema.DateTimeField(default=datetime.now)
910

1011
class Agent(TimestampDocument):
1112
type = schema.TextField(default='agent')
12-
post_ids = schema.ListField(schema.TextField())
13-
link_ids = schema.ListField(schema.TextField())
14-
domain_ids = schema.ListField(schema.TextField())
13+
message_ids = schema.ListField(schema.TextField())
14+
#domain_ids = schema.ListField(schema.TextField())
15+
#refresh_times = schema.ListField(schema.TextField())
1516

1617
class User(Agent):
1718
type = schema.TextField(default='user')
@@ -24,19 +25,27 @@ class User(Agent):
2425
# msg_id = schema.TextField(),
2526
# ts = schema.DateTimeField()
2627
# )))
27-
28+
# @schema.View.define('user')
29+
# def by_name(doc):
30+
# yield doc['username'], doc
31+
by_name = schema.View('user', '''\
32+
function(doc) {
33+
emit(doc.username, doc);
34+
}''')
35+
2836
class Message(TimestampDocument):
2937
agent_id = schema.TextField()
3038
title = schema.TextField()
3139
body = schema.TextField()
32-
link = schema.TextField()
40+
links = schema.ListField(schema.TextField())
3341
channels = schema.ListField(schema.TextField())
42+
addressee_ids = schema.ListField(schema.TextField())
3443
upvote_ids = schema.ListField(schema.TextField())
3544
# upvotes = schema.ListField(schema.DictField(schema.Schema.build( #optomization ?
3645
# agent_id = schema.TextField(),
3746
# ts = schema.DateTimeField()
3847
# )))
39-
parent_id = schema.TextField()
48+
parent_id = schema.TextField() # None unless self() is response
4049
response_ids = schema.ListField(schema.TextField())
4150

4251
def hours_old(self):
@@ -66,19 +75,56 @@ class AgentDomain(TimestampDocument):
6675
link = schema.TextField()
6776
agent_id = schema.TextField()
6877

69-
class Channel(TimestampDocument):
78+
class Tag(TimestampDocument):
79+
type = schema.TextField(default='tag')
80+
message_ids = schema.ListField(schema.TextField())
81+
82+
class Channel(Tag):
7083
type = schema.TextField(default='channel')
71-
name = schema.TextField()
84+
#name = schema.TextField()
85+
86+
class Link(Tag):
87+
type = schema.TextField(default='link')
88+
#url = schema.TextField()
7289

73-
# class Link(Channel):
74-
# type = schema.TextField(default='link')
90+
LINK_RE = re.compile("http[s]?://[\S]+", re.IGNORECASE) # http://www.ietf.org/rfc/rfc3986.txt
91+
CHANNEL_RE = re.compile("#(\w+)", re.IGNORECASE)
92+
ADDRESSEE_RE = re.compile("@(\w+)", re.IGNORECASE)
93+
94+
def parse_links(text):
95+
return LINK_RE.findall(text)
96+
97+
def parse_channels(text):
98+
return CHANNEL_RE.findall(text)
99+
100+
def parse_addressess(text):
101+
return ADDRESSEE_RE.findall(text)
102+
103+
def extract_tags(text):
104+
return {"links": parse_links(text), "channels": parse_channels(text)}
75105

76106
def create_user(db, username, password):
107+
#todo handle username already exists case
77108
user = User(username=username, hashpass=hashlib.sha224(password).hexdigest())
78109
user.store(db)
79110
return user
80111

81-
def create_message(db, agent, title, link, channels, body):
112+
def create_message(db, agent, text):
113+
raw_tags = extract_tags(text)
114+
msg = Message(agent_id=agent.id, body=text,
115+
channels=raw_tags['channels'], links=raw_tags['links'])
116+
msg.store(db)
117+
agent.message_ids.append(msg.id)
118+
agent.store(db)
119+
for link in [get_create_link(db, url) for url in msg.links]:
120+
link.message_ids.append(msg.id)
121+
link.store(db)
122+
for chan in [get_create_channel(db, name) for name in msg.channels]:
123+
chan.message_ids.append(msg.id)
124+
chan.store(db)
125+
return msg
126+
127+
def _create_message(db, agent, title, link, channels, body):
82128
channel = get_channel(db, link)
83129
if channel:
84130
# already exists, so agreement with owning agent
@@ -88,30 +134,23 @@ def create_message(db, agent, title, link, channels, body):
88134
pass
89135
msg = Message(agent_id=agent.id, title=title, channels=channels, body=body, link=link)
90136
msg.store(db)
91-
agent.post_ids.append(msg.id)
137+
agent.message_ids.append(msg.id)
92138
agent.store(db)
93139
return msg
94140

95-
def get_channel(db, name):
96-
return Channel.load(db, name)
141+
def get_create_tag(db, id, type='tag'):
142+
tag = Tag.load(db, id)
143+
if tag:
144+
return tag
145+
tag = Tag(id=id, type=type)
146+
tag.store(db)
147+
return tag
97148

98149
def get_create_channel(db, name):
99-
chan = get_channel(db, name)
100-
if chan:
101-
return chan
102-
chan = Channel(_id=name)
103-
chan.store(db)
104-
return chan
105-
106-
def get_create_link(db, url, agent):
107-
link = get_channel(db, url)
108-
if link:
109-
return link
110-
link = Link(_id=url, agent_id=agent.id)
111-
link.store(db)
112-
agent.links.append(url)
113-
agent.store(db)
114-
return link
150+
return get_create_tag(db, name, type='channel')
151+
152+
def get_create_link(db, url):
153+
return get_create_tag(db, url, type='link')
115154

116155
def upvote_message(db, agent, msg):
117156
""" Queued process? """
@@ -125,14 +164,22 @@ def upvote_message(db, agent, msg):
125164
calculate_influence(db, msg, vote)
126165
return vote
127166

128-
def respond_message(db, msg, agent, title, link, channels, body):
167+
def _respond_message(db, msg, agent, title, link, channels, body):
129168
resp = create_message(db, agent, title, link, channels, body)
130169
resp.parent_id = msg.id
131170
resp.store(db)
132171
msg.response_ids.append(resp.id)
133172
msg.store(db)
134173
return resp
135174

175+
def respond_message(db, msg, agent, text):
176+
resp = create_message(db, agent, text)
177+
resp.parent_id = msg.id
178+
resp.store(db)
179+
msg.response_ids.append(resp.id)
180+
msg.store(db)
181+
return resp
182+
136183
def calculate_influence(db, msg, vote):
137184
""" Queued process """
138185
owner = User.load(db, msg.agent_id)
@@ -144,6 +191,7 @@ def calculate_influence(db, msg, vote):
144191

145192
def score(msg, user):
146193
# todo: account for influence
194+
# negative influece ignored to prevent gaming system
147195
return len(msg.upvote_ids) - msg.hours_old()
148196

149197
def rank(msg_scores):
@@ -158,10 +206,41 @@ def rank(msg_scores):
158206
import unittest
159207

160208
DB_NAME = 'splice-tests'
161-
# probably want separate dbs for agents, messages, and votes
209+
# probably want separate dbs for agents, messages, votes, and tags
162210
# but not sure yet how that will affect views, etc.
163211

164-
class Test(unittest.TestCase):
212+
class ParseTest(unittest.TestCase):
213+
214+
def test_parse_links_embedded(self):
215+
links = parse_links('blah http://foo blah blah hTTps://bar.com blah')
216+
self.assertTrue(2, len(links))
217+
218+
def test_parse_links_corners(self):
219+
links = parse_links('http://foo blah blah TTps://bar.com')
220+
self.assertTrue(1, len(links))
221+
links = parse_links('http://foo blah blah HTTps://bar.com')
222+
self.assertTrue(2, len(links))
223+
224+
def test_parse_channels_embedded(self):
225+
chans = parse_channels('blah #food blah blah #clothes blah')
226+
self.assertTrue(2, len(chans))
227+
self.assertEqual("food", chans[0])
228+
229+
def test_parse_channels_corners(self):
230+
chans = parse_links('#food blah blah buildings')
231+
self.assertTrue(1, len(chans))
232+
links = parse_links('#finecuisine blah blah #catering')
233+
self.assertTrue(2, len(chans))
234+
235+
def test_extract_tags(self):
236+
text = "http://couchdb.org/id/1234 This article is great! #erlang #couchdb"
237+
tags = extract_tags(text)
238+
self.assertEqual(2, len(tags['channels']))
239+
self.assertEqual('http://couchdb.org/id/1234', tags['links'][0])
240+
241+
242+
243+
class DBTest(unittest.TestCase):
165244

166245
def setUp(self):
167246
uri = os.environ.get('COUCHDB_URI', 'http://localhost:5984/')
@@ -175,6 +254,16 @@ def setUp(self):
175254
# if DB_NAME in self.server:
176255
# del self.server[DB_NAME]
177256

257+
def test_get_create_tag(self):
258+
tag = get_create_channel(self.db, 'cats')
259+
self.assertEqual('channel', tag.type)
260+
self.assertEqual('cats', tag.id)
261+
262+
def test_get_create_link(self):
263+
tag = get_create_link(self.db, 'http://example.com')
264+
self.assertEqual('link', tag.type)
265+
self.assertEqual('http://example.com', tag.id)
266+
178267
def test_agent_creation(self):
179268
agent = Agent()
180269
assert agent.ts is not None
@@ -187,17 +276,45 @@ def test_create_user(self):
187276
assert user.id is not None
188277
self.assertEqual('name', user.username)
189278

190-
def test_create_message(self):
279+
def test_user_by_name(self):
280+
user = create_user(self.db, 'myname', 'password')
281+
res = User.by_name(self.db, include_docs=True)
282+
print len(res.rows)
283+
284+
def test_create_simple_message(self):
285+
user = create_user(self.db, 'name', 'password')
286+
assert user.id is not None
287+
msg = create_message(self.db, user, "Official python site http://python.org #python")
288+
assert msg.id is not None
289+
self.assertEqual(msg.id, user.message_ids[0])
290+
assert 'python' in msg.channels
291+
assert 'http://python.org' in msg.links
292+
293+
def test_create_multichannel_message(self):
294+
user = create_user(self.db, 'name', 'password')
295+
assert user.id is not None
296+
msg = create_message(self.db, user, "#erlang Interesting concept in data storage http://couchdb.org #json")
297+
assert msg.id is not None
298+
self.assertEqual(msg.id, user.message_ids[0])
299+
assert 'erlang' in msg.channels
300+
assert 'json' in msg.channels
301+
assert 'http://couchdb.org' in msg.links
302+
303+
def test_create_multilink_message(self):
191304
user = create_user(self.db, 'name', 'password')
192305
assert user.id is not None
193-
msg = create_message(self.db, user, "Go here!", "http://...", [], "Wow, check it.")
306+
msg = create_message(self.db, user, "http://blog.poundbang.in/post/132952897/couchdb-naked http://damienkatz.net/2008/02/incremental_map.html are two interesting links on #couchdb, #erlang and #twitter ")
194307
assert msg.id is not None
195-
self.assertEqual(msg.id, user.post_ids[0])
308+
self.assertEqual(msg.id, user.message_ids[0])
309+
assert 'erlang' in msg.channels
310+
assert 'couchdb' in msg.channels
311+
assert 'twitter' in msg.channels
312+
self.assertEqual(2, len(msg.links))
196313

197314
def test_upvote_message(self):
198315
user = create_user(self.db, 'user', 'password')
199316
user2 = create_user(self.db, 'user2', 'password')
200-
msg = create_message(self.db, user, "Go here!", "http://...", [], "Wow, check it.")
317+
msg = create_message(self.db, user, "Official python site http://python.org #python")
201318
vote = upvote_message(self.db, user2, msg)
202319
assert vote.id is not None
203320
user = User.load(self.db, user.id) # ensure latest version
@@ -206,8 +323,8 @@ def test_upvote_message(self):
206323
def test_respond_message(self):
207324
user = create_user(self.db, 'user', 'password')
208325
user2 = create_user(self.db, 'user2', 'password')
209-
msg = create_message(self.db, user, "Go here!", "http://...", [], "Wow, check it.")
210-
resp = respond_message(self.db, msg, user2, "First Post!", None, [], "Blah, blah")
326+
msg = create_message(self.db, user, "Official python site http://python.org #python")
327+
resp = respond_message(self.db, msg, user2, "First Post!")
211328
self.assertEqual(msg.id, resp.parent_id)
212329

213330
class RankTest(unittest.TestCase):

0 commit comments

Comments
 (0)