3 # Copyright 2010 Google Inc. All Rights Reserved.
5 """A simple example of matcher API."""
7 __author__
= 'bwydrowski@google.com (Bartek Wydrowski)'
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."""
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
):
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."""
68 user
= users
.get_current_user()
70 self
.redirect(users
.create_login_url(self
.request
.url
))
75 user
= users
.get_current_user()
77 self
.redirect(users
.create_login_url(self
.request
.url
))
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())
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
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)
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
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
143 state
= 'UNREGISTERED'
145 for sub
in subscriptions
:
146 if sub
[0] == subscription_id
:
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
,
164 time
.ctime(expiration_time
),
169 self
.response
.out
.write(FOOTER
)
173 application
= webapp
.WSGIApplication([('/', MatcherDemo
),
174 ('/_ah/matcher', IncHitCounter
)],
176 util
.run_wsgi_app(application
)
180 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
181 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
184 <title>Matcher demo</title>
185 <style type="text/css">
188 font-family:sans-serif;
193 border: 1px solid Navy;
195 background-color: LightSteelBlue;
196 font-family:sans-serif;
200 font-family: Courier New, Courier, mono;
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.
220 <form action="/" method="POST">
222 <tr><td>field_text_a:</td>
223 <td><TEXTAREA name="field_text_a" rows="4" cols="80"></TEXTAREA></td>
225 <tr><td>field_text_b:</td>
226 <td><TEXTAREA name="field_text_b" rows="4" cols="80"></TEXTAREA></td>
228 <tr><td>field_int32_a:</td>
229 <td><input type="text" name="field_int32_a" size=16 value="0"></td>
231 <tr><td>field_int32_b:</td>
232 <td><input type="text" name="field_int32_b" size=16 value="0"></td>
234 <tr><td>field_float_a:</td>
235 <td><input type="text" name="field_float_a" size=16 value="0.0"></td>
237 <tr><td>field_float_b:</td>
238 <td><input type="text" name="field_float_b" size=16 value="0.0"></td>
240 <tr><td><input type="submit" name="match" value="Match"></td>
246 <input type=button value="Refresh subscription state"
247 onClick="window.location.reload()">
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.
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>
276 if __name__
== '__main__':