Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit daa096e

Browse filesBrowse files
committed
files for chapter 18B - async/await
1 parent 5e3d379 commit daa096e
Copy full SHA for daa096e

File tree

Expand file treeCollapse file tree

10 files changed

+662
-0
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+662
-0
lines changed

‎18b-async-await/README.rst

Copy file name to clipboard
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Sample code for Chapter 18 - "Concurrency with asyncio"
2+
3+
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
4+
http://shop.oreilly.com/product/0636920032519.do
+223Lines 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]))
+19Lines 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>
+72Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
html = template.format(query=query, result=res, # <5>
41+
message=msg)
42+
print('Sending {} results'.format(len(descriptions))) # <6>
43+
return web.Response(content_type=CONTENT_TYPE, text=html) # <7>
44+
# END HTTP_CHARFINDER_HOME
45+
46+
47+
# BEGIN HTTP_CHARFINDER_SETUP
48+
@asyncio.coroutine
49+
def init(loop, address, port): # <1>
50+
app = web.Application(loop=loop) # <2>
51+
app.router.add_route('GET', '/', home) # <3>
52+
handler = app.make_handler() # <4>
53+
server = yield from loop.create_server(handler,
54+
address, port) # <5>
55+
return server.sockets[0].getsockname() # <6>
56+
57+
def main(address="127.0.0.1", port=8888):
58+
port = int(port)
59+
loop = asyncio.get_event_loop()
60+
host = loop.run_until_complete(init(loop, address, port)) # <7>
61+
print('Serving on {}. Hit CTRL-C to stop.'.format(host))
62+
try:
63+
loop.run_forever() # <8>
64+
except KeyboardInterrupt: # CTRL+C pressed
65+
pass
66+
print('Server shutting down.')
67+
loop.close() # <9>
68+
69+
70+
if __name__ == '__main__':
71+
main(*sys.argv[1:])
72+
# END HTTP_CHARFINDER_SETUP
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
3+
# BEGIN TCP_CHARFINDER_TOP
4+
import sys
5+
import asyncio
6+
7+
from charfinder import UnicodeNameIndex # <1>
8+
9+
CRLF = b'\r\n'
10+
PROMPT = b'?> '
11+
12+
index = UnicodeNameIndex() # <2>
13+
14+
@asyncio.coroutine
15+
def handle_queries(reader, writer): # <3>
16+
while True: # <4>
17+
writer.write(PROMPT) # can't yield from! # <5>
18+
yield from writer.drain() # must yield from! # <6>
19+
data = yield from reader.readline() # <7>
20+
try:
21+
query = data.decode().strip()
22+
except UnicodeDecodeError: # <8>
23+
query = '\x00'
24+
client = writer.get_extra_info('peername') # <9>
25+
print('Received from {}: {!r}'.format(client, query)) # <10>
26+
if query:
27+
if ord(query[:1]) < 32: # <11>
28+
break
29+
lines = list(index.find_description_strs(query)) # <12>
30+
if lines:
31+
writer.writelines(line.encode() + CRLF for line in lines) # <13>
32+
writer.write(index.status(query, len(lines)).encode() + CRLF) # <14>
33+
34+
yield from writer.drain() # <15>
35+
print('Sent {} results'.format(len(lines))) # <16>
36+
37+
print('Close the client socket') # <17>
38+
writer.close() # <18>
39+
# END TCP_CHARFINDER_TOP
40+
41+
# BEGIN TCP_CHARFINDER_MAIN
42+
def main(address='127.0.0.1', port=2323): # <1>
43+
port = int(port)
44+
loop = asyncio.get_event_loop()
45+
server_coro = asyncio.start_server(handle_queries, address, port,
46+
loop=loop) # <2>
47+
server = loop.run_until_complete(server_coro) # <3>
48+
49+
host = server.sockets[0].getsockname() # <4>
50+
print('Serving on {}. Hit CTRL-C to stop.'.format(host)) # <5>
51+
try:
52+
loop.run_forever() # <6>
53+
except KeyboardInterrupt: # CTRL+C pressed
54+
pass
55+
56+
print('Server shutting down.')
57+
server.close() # <7>
58+
loop.run_until_complete(server.wait_closed()) # <8>
59+
loop.close() # <9>
60+
61+
62+
if __name__ == '__main__':
63+
main(*sys.argv[1:]) # <10>
64+
# END TCP_CHARFINDER_MAIN

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.