update to reflect API changes
[gae-samples.git] / matcher-sample / matcher.py
blobecf2f39e17f53721f11ab3b60b50bf5abb71a708
1 #!/usr/bin/python2.4
3 # Copyright 2010 Google Inc. All Rights Reserved.
5 """A simple example of matcher API."""
7 __author__ = 'bwydrowski@google.com (Bartek Wydrowski)'
9 import cgi
10 import sys
11 import time
13 from google.appengine.ext import webapp
14 from google.appengine.ext.webapp import util
15 from google.appengine.api import matcher
16 from google.appengine.api import users
17 from google.appengine.ext import db
20 # these are defined at the bottom
21 HEADER1 = HEADER2 = FOOTER = None
24 class MatcherDocument(db.Model):
25 """MatcherDocument is the class sent to the matcher.Match function."""
26 field_text_a = db.StringProperty()
27 field_text_b = db.StringProperty()
28 field_int32_a = db.IntegerProperty()
29 field_int32_b = db.IntegerProperty()
30 field_float_a = db.FloatProperty()
31 field_float_b = db.FloatProperty()
34 class SubscriptionInfo(db.Model):
35 """SubscriptionInfo stores queries and hitcounts per subscription id."""
36 subscription_id = db.StringProperty()
37 hit_count = db.IntegerProperty()
38 user_id = db.StringProperty()
41 class IncHitCounter(webapp.RequestHandler):
42 """IncHitCounter handles match result events by incrementing hit count."""
44 def post(self):
45 sub_ids = self.request.get_all('id')
46 doc = matcher.get_document(self.request)
47 user_id = self.request.get('topic')
48 assert isinstance(doc, MatcherDocument)
49 for sub_id in sub_ids:
50 subscription_info = db.GqlQuery('SELECT * FROM SubscriptionInfo WHERE' +
51 ' subscription_id = :1 AND user_id = :2', sub_id, user_id).get()
52 if not subscription_info: continue
53 db.run_in_transaction(self.__Increment, subscription_info.key())
55 def __Increment(self, key):
56 if key:
57 subscription_info = db.get(key)
58 if not subscription_info:
59 subscription_info = SubscriptionInfo()
60 subscription_info.hit_count += 1
61 db.put(subscription_info)
64 class MatcherDemo(webapp.RequestHandler):
65 """This class is a UI for matcher to demonstrate its capabilities."""
67 def get(self):
68 user = users.get_current_user()
69 if not user:
70 self.redirect(users.create_login_url(self.request.url))
71 return
72 self.__Render(user)
74 def post(self):
75 user = users.get_current_user()
76 if not user:
77 self.redirect(users.create_login_url(self.request.url))
78 return
80 """Handler for match, subscribe or unsubscribe form button press."""
81 # Match submit button was pressed
82 if self.request.get('match') == 'Match':
83 doc = MatcherDocument()
84 doc.field_text_a = str(self.request.get('field_text_a'))
85 doc.field_text_b = str(self.request.get('field_text_b'))
86 doc.field_int32_a = int(self.request.get('field_int32_a'))
87 doc.field_int32_b = int(self.request.get('field_int32_b'))
88 doc.field_float_a = float(self.request.get('field_float_a'))
89 doc.field_float_b = float(self.request.get('field_float_b'))
90 matcher.match(doc, topic = user.user_id())
92 # Subscribe submit button was pressed
93 elif self.request.get('subscribe') == 'Subscribe':
94 subscription_id = self.request.get('subscription_id')
95 query = self.request.get('query')
96 matcher.subscribe(MatcherDocument, query, subscription_id,
97 topic = user.user_id())
98 self.__ResetSubscriptionInfo(subscription_id, user.user_id())
100 # Unsubscribe submit button was pressed
101 elif self.request.get('unsubscribe') == 'Unsubscribe':
102 subscription_id = self.request.get('subscription_id')
103 matcher.unsubscribe(MatcherDocument, subscription_id,
104 topic = user.user_id())
105 self.__ResetSubscriptionInfo(subscription_id, user.user_id())
107 self.__Render(user)
109 def __GetSubscriptionInfo(self, subscription_id, user_id):
110 sub_info = db.GqlQuery('SELECT * FROM SubscriptionInfo WHERE' +
111 ' subscription_id = :1 AND user_id = :2',
112 subscription_id, user_id).get()
113 if sub_info: return sub_info.hit_count
114 return 0
116 def __ResetSubscriptionInfo(self, subscription_id, user_id):
117 # Delete existing instance(s) of subscription
118 subs = db.GqlQuery('SELECT * FROM SubscriptionInfo WHERE' +
119 ' subscription_id = :1 AND user_id = :2',
120 subscription_id, user_id).fetch(100)
121 for sub in subs:
122 sub.delete()
123 # Insert new subscription instance
124 sub_info = SubscriptionInfo()
125 sub_info.subscription_id = subscription_id
126 sub_info.hit_count = 0
127 sub_info.user_id = user_id
128 sub_info.put()
130 def __Render(self, user):
131 self.response.out.write(HEADER1)
132 self.response.out.write("<h2>Matcher demo welcomes %s! "
133 "<a href=\"%s\">sign out</a></h2>" %
134 (user.nickname(), users.create_logout_url("/")))
135 self.response.out.write(HEADER2)
136 subscriptions = matcher.list_subscriptions(MatcherDocument,
137 topic = user.user_id())
138 subscription_prefix = 'subscription_'
139 for subscription_suffix in ['a', 'b', 'c', 'd']:
140 subscription_id = subscription_prefix + subscription_suffix
141 query = ''
142 expiration_time = 0
143 state = 'UNREGISTERED'
144 error = ''
145 for sub in subscriptions:
146 if sub[0] == subscription_id:
147 state_code = 0
148 (sub_id, query, expiration_time, state_code, error) = sub
149 state = matcher.matcher_pb.SubscriptionRecord.State_Name(state_code)
151 hit_count = self.__GetSubscriptionInfo(subscription_id, user.user_id())
152 self.response.out.write("""
153 <div class="form"><form action="/" method="POST">
154 %s <input type="text" name="query" value="%s" size=128><br>
155 Expiration time <input type="text" value="%s" disabled="disabled">
156 State <input type="text" value="%s" disabled="disabled">
157 Error <input type="text" value="%s" disabled="disabled"><br>
158 Hits <input type="text" value="%d" disabled="disabled"><br>
159 <input type="submit" name="subscribe" value="Subscribe">
160 <input type="submit" name="unsubscribe" value="Unsubscribe">
161 <input type="hidden" name="subscription_id" value="%s">
162 </form></div>""" % (subscription_id,
163 cgi.escape(query),
164 time.ctime(expiration_time),
165 state,
166 error,
167 hit_count,
168 subscription_id))
169 self.response.out.write(FOOTER)
172 def main(argv):
173 application = webapp.WSGIApplication([('/', MatcherDemo),
174 ('/_ah/matcher', IncHitCounter)],
175 debug=True)
176 util.run_wsgi_app(application)
179 HEADER1 = """
180 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
181 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
182 <html>
183 <head>
184 <title>Matcher demo</title>
185 <style type="text/css">
186 body {
187 margin: 15px;
188 font-family:sans-serif;
190 div.form {
191 margin: 10px;
192 padding: 10px;
193 border: 1px solid Navy;
194 width: auto;
195 background-color: LightSteelBlue;
196 font-family:sans-serif;
198 pre {
199 margin-left: 1.0em;
200 font-family: Courier New, Courier, mono;
201 color: #09571b;
203 </style>
204 </head>
205 <body>
206 <div id="main">
207 <div id="body">
210 HEADER2 = """
211 This demo allows you to experiment with the Matcher Appengine API using
212 a small number subscriptions and a simple document.
213 You can enter queries into the subscription_* fields and press the 'Subscribe'
214 button to register them.
215 Then enter some values into the document that may match these subscriptions
216 and press the 'Match' button. Subscriptions that match will have their hit count
217 incremented once the match results arrive from the task queue.
218 Press the refresh button to see the latest counts.
219 <div class="form">
220 <form action="/" method="POST">
221 <table>
222 <tr><td>field_text_a:</td>
223 <td><TEXTAREA name="field_text_a" rows="4" cols="80"></TEXTAREA></td>
224 </tr>
225 <tr><td>field_text_b:</td>
226 <td><TEXTAREA name="field_text_b" rows="4" cols="80"></TEXTAREA></td>
227 </tr>
228 <tr><td>field_int32_a:</td>
229 <td><input type="text" name="field_int32_a" size=16 value="0"></td>
230 </tr>
231 <tr><td>field_int32_b:</td>
232 <td><input type="text" name="field_int32_b" size=16 value="0"></td>
233 </tr>
234 <tr><td>field_float_a:</td>
235 <td><input type="text" name="field_float_a" size=16 value="0.0"></td>
236 </tr>
237 <tr><td>field_float_b:</td>
238 <td><input type="text" name="field_float_b" size=16 value="0.0"></td>
239 </tr>
240 <tr><td><input type="submit" name="match" value="Match"></td>
241 </tr>
242 </table>
243 </form>
244 </div>
245 <form>
246 <input type=button value="Refresh subscription state"
247 onClick="window.location.reload()">
248 </form>
251 FOOTER = """
252 </div></div>
253 <div>
254 <h2>Subscription query language summary</h2>
255 <h3>Numeric comparison operators</h3>
256 The > >=, =, <= and < numeric operators are available. For example: <br>
257 <pre>field_int32_a > 20</pre>
259 <h3>Text operators</h3>
260 Text fields can be matched for the occurance of a word or phrase anywhere in the
261 content of the field. For example:
262 <pre>field_text_a:horse</pre>
263 <pre>field_text_a:"horse riding"</pre>
265 <h3>Logical operators</h3>
266 Predicates can be combined with the NOT, OR and AND operators.
267 They can also be grouped using parantheses.
268 For example: <br>
269 <pre>field_int32_a > 20 AND field_int32_b = 10</pre>
270 <pre>(field_int32_a > 20 AND field_int32_b = 10) OR field_text_a:fox</pre>
271 </div>
272 </body>
273 </html>
276 if __name__ == '__main__':
277 main(sys.argv)