3
3
from couchdb import client , schema
4
4
from datetime import datetime
5
5
import hashlib
6
+ import re
6
7
7
8
class TimestampDocument (schema .Document ):
8
9
ts = schema .DateTimeField (default = datetime .now )
9
10
10
11
class Agent (TimestampDocument ):
11
12
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())
15
16
16
17
class User (Agent ):
17
18
type = schema .TextField (default = 'user' )
@@ -24,19 +25,27 @@ class User(Agent):
24
25
# msg_id = schema.TextField(),
25
26
# ts = schema.DateTimeField()
26
27
# )))
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
+
28
36
class Message (TimestampDocument ):
29
37
agent_id = schema .TextField ()
30
38
title = schema .TextField ()
31
39
body = schema .TextField ()
32
- link = schema .TextField ()
40
+ links = schema .ListField ( schema . TextField () )
33
41
channels = schema .ListField (schema .TextField ())
42
+ addressee_ids = schema .ListField (schema .TextField ())
34
43
upvote_ids = schema .ListField (schema .TextField ())
35
44
# upvotes = schema.ListField(schema.DictField(schema.Schema.build( #optomization ?
36
45
# agent_id = schema.TextField(),
37
46
# ts = schema.DateTimeField()
38
47
# )))
39
- parent_id = schema .TextField ()
48
+ parent_id = schema .TextField () # None unless self() is response
40
49
response_ids = schema .ListField (schema .TextField ())
41
50
42
51
def hours_old (self ):
@@ -66,19 +75,56 @@ class AgentDomain(TimestampDocument):
66
75
link = schema .TextField ()
67
76
agent_id = schema .TextField ()
68
77
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 ):
70
83
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()
72
89
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 )}
75
105
76
106
def create_user (db , username , password ):
107
+ #todo handle username already exists case
77
108
user = User (username = username , hashpass = hashlib .sha224 (password ).hexdigest ())
78
109
user .store (db )
79
110
return user
80
111
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 ):
82
128
channel = get_channel (db , link )
83
129
if channel :
84
130
# already exists, so agreement with owning agent
@@ -88,30 +134,23 @@ def create_message(db, agent, title, link, channels, body):
88
134
pass
89
135
msg = Message (agent_id = agent .id , title = title , channels = channels , body = body , link = link )
90
136
msg .store (db )
91
- agent .post_ids .append (msg .id )
137
+ agent .message_ids .append (msg .id )
92
138
agent .store (db )
93
139
return msg
94
140
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
97
148
98
149
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' )
115
154
116
155
def upvote_message (db , agent , msg ):
117
156
""" Queued process? """
@@ -125,14 +164,22 @@ def upvote_message(db, agent, msg):
125
164
calculate_influence (db , msg , vote )
126
165
return vote
127
166
128
- def respond_message (db , msg , agent , title , link , channels , body ):
167
+ def _respond_message (db , msg , agent , title , link , channels , body ):
129
168
resp = create_message (db , agent , title , link , channels , body )
130
169
resp .parent_id = msg .id
131
170
resp .store (db )
132
171
msg .response_ids .append (resp .id )
133
172
msg .store (db )
134
173
return resp
135
174
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
+
136
183
def calculate_influence (db , msg , vote ):
137
184
""" Queued process """
138
185
owner = User .load (db , msg .agent_id )
@@ -144,6 +191,7 @@ def calculate_influence(db, msg, vote):
144
191
145
192
def score (msg , user ):
146
193
# todo: account for influence
194
+ # negative influece ignored to prevent gaming system
147
195
return len (msg .upvote_ids ) - msg .hours_old ()
148
196
149
197
def rank (msg_scores ):
@@ -158,10 +206,41 @@ def rank(msg_scores):
158
206
import unittest
159
207
160
208
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
162
210
# but not sure yet how that will affect views, etc.
163
211
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 ):
165
244
166
245
def setUp (self ):
167
246
uri = os .environ .get ('COUCHDB_URI' , 'http://localhost:5984/' )
@@ -175,6 +254,16 @@ def setUp(self):
175
254
# if DB_NAME in self.server:
176
255
# del self.server[DB_NAME]
177
256
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
+
178
267
def test_agent_creation (self ):
179
268
agent = Agent ()
180
269
assert agent .ts is not None
@@ -187,17 +276,45 @@ def test_create_user(self):
187
276
assert user .id is not None
188
277
self .assertEqual ('name' , user .username )
189
278
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 ):
191
304
user = create_user (self .db , 'name' , 'password' )
192
305
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 " )
194
307
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 ))
196
313
197
314
def test_upvote_message (self ):
198
315
user = create_user (self .db , 'user' , 'password' )
199
316
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 " )
201
318
vote = upvote_message (self .db , user2 , msg )
202
319
assert vote .id is not None
203
320
user = User .load (self .db , user .id ) # ensure latest version
@@ -206,8 +323,8 @@ def test_upvote_message(self):
206
323
def test_respond_message (self ):
207
324
user = create_user (self .db , 'user' , 'password' )
208
325
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!" )
211
328
self .assertEqual (msg .id , resp .parent_id )
212
329
213
330
class RankTest (unittest .TestCase ):
0 commit comments