Skip to content

Commit 2d7a967

Browse files
committed
final concurrency examples
1 parent 39e87de commit 2d7a967

26 files changed

+1227
-477
lines changed

futures/callbackhell.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
fetch1(request1, function (response1) {
2+
// phase 1
3+
var request2 = step1(response1);
4+
5+
fetch2(request2, function (response2) {
6+
// phase 2
7+
var request3 = step2(response2);
8+
9+
fetch3(request3, function (response3) {
10+
// phase 3
11+
step3(response3);
12+
});
13+
});
14+
});

futures/callbackhell.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
def phase1(response1):
2+
request2 = step1(response1)
3+
fetch2(request2, phase2)
4+
5+
6+
def phase2(response2):
7+
request3 = step2(response2)
8+
fetch3(request3, phase3)
9+
10+
11+
def phase3(response3):
12+
step3(response3)
13+
14+
15+
fetch1(request1, phase1)

futures/charfinder/charfinder.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Unicode character finder utility:
5+
find characters based on words in their official names.
6+
7+
This can be used from the command line, just pass words as arguments.
8+
9+
Here is the ``main`` function which makes it happen::
10+
11+
>>> main('rook') # doctest: +NORMALIZE_WHITESPACE
12+
U+2656 ♖ WHITE CHESS ROOK
13+
U+265C ♜ BLACK CHESS ROOK
14+
(2 matches for 'rook')
15+
>>> main('rook', 'black') # doctest: +NORMALIZE_WHITESPACE
16+
U+265C ♜ BLACK CHESS ROOK
17+
(1 match for 'rook black')
18+
>>> main('white bishop') # doctest: +NORMALIZE_WHITESPACE
19+
U+2657 ♗ WHITE CHESS BISHOP
20+
(1 match for 'white bishop')
21+
>>> main("jabberwocky's vest")
22+
(No match for "jabberwocky's vest")
23+
24+
25+
For exploring words that occur in the character names, there is the
26+
``word_report`` function::
27+
28+
>>> index = UnicodeNameIndex(sample_chars)
29+
>>> index.word_report()
30+
3 SIGN
31+
2 A
32+
2 EURO
33+
2 LATIN
34+
2 LETTER
35+
1 CAPITAL
36+
1 CURRENCY
37+
1 DOLLAR
38+
1 SMALL
39+
>>> index = UnicodeNameIndex()
40+
>>> index.word_report(10)
41+
75821 CJK
42+
75761 IDEOGRAPH
43+
74656 UNIFIED
44+
13196 SYLLABLE
45+
11735 HANGUL
46+
7616 LETTER
47+
2232 WITH
48+
2180 SIGN
49+
2122 SMALL
50+
1709 CAPITAL
51+
52+
Note: characters with names starting with 'CJK UNIFIED IDEOGRAPH'
53+
are indexed with those three words only, excluding the hexadecimal
54+
codepoint at the end of the name.
55+
56+
"""
57+
58+
import sys
59+
import re
60+
import unicodedata
61+
import pickle
62+
import warnings
63+
import itertools
64+
import functools
65+
from collections import namedtuple
66+
67+
RE_WORD = re.compile('\w+')
68+
RE_UNICODE_NAME = re.compile('^[A-Z0-9 -]+$')
69+
RE_CODEPOINT = re.compile('U\+([0-9A-F]{4,6})')
70+
71+
INDEX_NAME = 'charfinder_index.pickle'
72+
MINIMUM_SAVE_LEN = 10000
73+
CJK_UNI_PREFIX = 'CJK UNIFIED IDEOGRAPH'
74+
CJK_CMP_PREFIX = 'CJK COMPATIBILITY IDEOGRAPH'
75+
76+
sample_chars = [
77+
'$', # DOLLAR SIGN
78+
'A', # LATIN CAPITAL LETTER A
79+
'a', # LATIN SMALL LETTER A
80+
'\u20a0', # EURO-CURRENCY SIGN
81+
'\u20ac', # EURO SIGN
82+
]
83+
84+
CharDescription = namedtuple('CharDescription', 'code_str char name')
85+
86+
QueryResult = namedtuple('QueryResult', 'count items')
87+
88+
89+
def tokenize(text):
90+
"""return iterable of uppercased words"""
91+
for match in RE_WORD.finditer(text):
92+
yield match.group().upper()
93+
94+
95+
def query_type(text):
96+
text_upper = text.upper()
97+
if 'U+' in text_upper:
98+
return 'CODEPOINT'
99+
elif RE_UNICODE_NAME.match(text_upper):
100+
return 'NAME'
101+
else:
102+
return 'CHARACTERS'
103+
104+
105+
class UnicodeNameIndex:
106+
107+
def __init__(self, chars=None):
108+
self.load(chars)
109+
110+
def load(self, chars=None):
111+
self.index = None
112+
if chars is None:
113+
try:
114+
with open(INDEX_NAME, 'rb') as fp:
115+
self.index = pickle.load(fp)
116+
except OSError:
117+
pass
118+
if self.index is None:
119+
self.build_index(chars)
120+
if len(self.index) > MINIMUM_SAVE_LEN:
121+
try:
122+
self.save()
123+
except OSError as exc:
124+
warnings.warn('Could not save {!r}: {}'
125+
.format(INDEX_NAME, exc))
126+
127+
def save(self):
128+
with open(INDEX_NAME, 'wb') as fp:
129+
pickle.dump(self.index, fp)
130+
131+
def build_index(self, chars=None):
132+
if chars is None:
133+
chars = (chr(i) for i in range(32, sys.maxunicode))
134+
index = {}
135+
for char in chars:
136+
try:
137+
name = unicodedata.name(char)
138+
except ValueError:
139+
continue
140+
if name.startswith(CJK_UNI_PREFIX):
141+
name = CJK_UNI_PREFIX
142+
elif name.startswith(CJK_CMP_PREFIX):
143+
name = CJK_CMP_PREFIX
144+
145+
for word in tokenize(name):
146+
index.setdefault(word, set()).add(char)
147+
148+
self.index = index
149+
150+
def word_rank(self, top=None):
151+
res = [(len(self.index[key]), key) for key in self.index]
152+
res.sort(key=lambda item: (-item[0], item[1]))
153+
if top is not None:
154+
res = res[:top]
155+
return res
156+
157+
def word_report(self, top=None):
158+
for postings, key in self.word_rank(top):
159+
print('{:5} {}'.format(postings, key))
160+
161+
def find_chars(self, query, start=0, stop=None):
162+
stop = sys.maxsize if stop is None else stop
163+
result_sets = []
164+
for word in tokenize(query):
165+
chars = self.index.get(word)
166+
if chars is None: # shorcut: no such word
167+
result_sets = []
168+
break
169+
result_sets.append(chars)
170+
171+
if not result_sets:
172+
return QueryResult(0, ())
173+
174+
result = functools.reduce(set.intersection, result_sets)
175+
result = sorted(result) # must sort to support start, stop
176+
result_iter = itertools.islice(result, start, stop)
177+
return QueryResult(len(result),
178+
(char for char in result_iter))
179+
180+
def describe(self, char):
181+
code_str = 'U+{:04X}'.format(ord(char))
182+
name = unicodedata.name(char)
183+
return CharDescription(code_str, char, name)
184+
185+
def find_descriptions(self, query, start=0, stop=None):
186+
for char in self.find_chars(query, start, stop).items:
187+
yield self.describe(char)
188+
189+
def get_descriptions(self, chars):
190+
for char in chars:
191+
yield self.describe(char)
192+
193+
def describe_str(self, char):
194+
return '{:7}\t{}\t{}'.format(*self.describe(char))
195+
196+
def find_description_strs(self, query, start=0, stop=None):
197+
for char in self.find_chars(query, start, stop).items:
198+
yield self.describe_str(char)
199+
200+
@staticmethod # not an instance method due to concurrency
201+
def status(query, counter):
202+
if counter == 0:
203+
msg = 'No match'
204+
elif counter == 1:
205+
msg = '1 match'
206+
else:
207+
msg = '{} matches'.format(counter)
208+
return '{} for {!r}'.format(msg, query)
209+
210+
211+
def main(*args):
212+
index = UnicodeNameIndex()
213+
query = ' '.join(args)
214+
n = 0
215+
for n, line in enumerate(index.find_description_strs(query), 1):
216+
print(line)
217+
print('({})'.format(index.status(query, n)))
218+
219+
if __name__ == '__main__':
220+
if len(sys.argv) > 1:
221+
main(*sys.argv[1:])
222+
else:
223+
print('Usage: {} word1 [word2]...'.format(sys.argv[0]))
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Charfinder</title>
6+
</head>
7+
<body>
8+
Examples: {links}
9+
<p>
10+
<form action="/">
11+
<input type="search" name="query" value="{query}">
12+
<input type="submit" value="find"> {message}
13+
</form>
14+
</p>
15+
<table>
16+
{result}
17+
</table>
18+
</body>
19+
</html>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import asyncio
5+
from aiohttp import web
6+
7+
from charfinder import UnicodeNameIndex
8+
9+
TEMPLATE_NAME = 'http_charfinder.html'
10+
CONTENT_TYPE = 'text/html; charset=UTF-8'
11+
SAMPLE_WORDS = ('bismillah chess cat circled Malayalam digit'
12+
' Roman face Ethiopic black mark symbol dot'
13+
' operator Braille hexagram').split()
14+
15+
ROW_TPL = '<tr><td>{code_str}</td><th>{char}</th><td>{name}</td></tr>'
16+
LINK_TPL = '<a href="/?query={0}" title="find &quot;{0}&quot;">{0}</a>'
17+
LINKS_HTML = ', '.join(LINK_TPL.format(word) for word in
18+
sorted(SAMPLE_WORDS, key=str.upper))
19+
20+
21+
index = UnicodeNameIndex()
22+
with open(TEMPLATE_NAME) as tpl:
23+
template = tpl.read()
24+
template = template.replace('{links}', LINKS_HTML)
25+
26+
# BEGIN HTTP_CHARFINDER_HOME
27+
def home(request): # <1>
28+
query = request.GET.get('query', '').strip() # <2>
29+
print('Query: {!r}'.format(query)) # <3>
30+
if query: # <4>
31+
descriptions = list(index.find_descriptions(query))
32+
res = '\n'.join(ROW_TPL.format(**vars(descr))
33+
for descr in descriptions)
34+
msg = index.status(query, len(descriptions))
35+
else:
36+
descriptions = []
37+
res = ''
38+
msg = 'Enter words describing characters.'
39+
40+
text = template.format(query=query, result=res, message=msg)
41+
print('Sending {} results'.format(len(descriptions)))
42+
return web.Response(content_type=CONTENT_TYPE, text=text)
43+
# END HTTP_CHARFINDER_HOME
44+
45+
46+
# BEGIN HTTP_CHARFINDER_SETUP
47+
@asyncio.coroutine
48+
def init(loop, address, port): # <1>
49+
app = web.Application(loop=loop) # <2>
50+
app.router.add_route('GET', '/', home) # <3>
51+
handler = app.make_handler() # <4>
52+
server = yield from loop.create_server(handler,
53+
address, port) # <5>
54+
return server.sockets[0].getsockname() # <6>
55+
56+
def main(address="127.0.0.1", port=8888):
57+
port = int(port)
58+
loop = asyncio.get_event_loop()
59+
host = loop.run_until_complete(init(loop, address, port)) # <7>
60+
print('Serving on {}. Hit CTRL-C to stop.'.format(host))
61+
try:
62+
loop.run_forever() # <8>
63+
except KeyboardInterrupt: # CTRL+C pressed
64+
pass
65+
print('Server shutting down.')
66+
loop.close() # <9>
67+
68+
69+
if __name__ == '__main__':
70+
main(*sys.argv[1:])
71+
# END HTTP_CHARFINDER_SETUP

0 commit comments

Comments
 (0)