update from O'Reilly repo
This commit is contained in:
4
22-async/README.rst
Normal file
4
22-async/README.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Sample code for Chapter 22 - "Asynchronous programming"
|
||||
|
||||
From the book "Fluent Python, Second Edition" by Luciano Ramalho (O'Reilly, 2021)
|
||||
https://learning.oreilly.com/library/view/fluent-python-2nd/9781492056348/
|
||||
28
22-async/domains/README.rst
Normal file
28
22-async/domains/README.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
domainlib demonstration
|
||||
=======================
|
||||
|
||||
Run Python's async console (requires Python ≥ 3.8)::
|
||||
|
||||
$ python3 -m asyncio
|
||||
|
||||
I'll see ``asyncio`` imported automatically::
|
||||
|
||||
>>> import asyncio
|
||||
|
||||
Now you can experiment with ``domainlib``.
|
||||
|
||||
At the `>>>` prompt, type these commands::
|
||||
|
||||
>>> from domainlib import *
|
||||
>>> await probe('python.org')
|
||||
|
||||
Note the result.
|
||||
|
||||
Next::
|
||||
|
||||
>>> names = 'python.org rust-lang.org golang.org n05uch1an9.org'.split()
|
||||
>>> async for result in multi_probe(names):
|
||||
... print(*result, sep='\t')
|
||||
|
||||
Note that if you run the last two lines again,
|
||||
the results are likely to appear in a different order.
|
||||
@@ -1,30 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
from curio import run, TaskGroup
|
||||
from curio.socket import getaddrinfo, gaierror
|
||||
import curio.socket as socket
|
||||
from keyword import kwlist
|
||||
|
||||
MAX_KEYWORD_LEN = 4 # <1>
|
||||
MAX_KEYWORD_LEN = 4
|
||||
|
||||
|
||||
async def probe(domain: str) -> tuple[str, bool]: # <2>
|
||||
async def probe(domain: str) -> tuple[str, bool]: # <1>
|
||||
try:
|
||||
await getaddrinfo(domain, None) # <4>
|
||||
except gaierror:
|
||||
await socket.getaddrinfo(domain, None) # <2>
|
||||
except socket.gaierror:
|
||||
return (domain, False)
|
||||
return (domain, True)
|
||||
|
||||
|
||||
async def main() -> None: # <5>
|
||||
names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN) # <6>
|
||||
domains = (f'{name}.dev'.lower() for name in names) # <7>
|
||||
async with TaskGroup() as group:
|
||||
async def main() -> None:
|
||||
names = (kw for kw in kwlist if len(kw) <= MAX_KEYWORD_LEN)
|
||||
domains = (f'{name}.dev'.lower() for name in names)
|
||||
async with TaskGroup() as group: # <3>
|
||||
for domain in domains:
|
||||
await group.spawn(probe, domain)
|
||||
async for task in group: # <9>
|
||||
domain, found = task.result # <10>
|
||||
await group.spawn(probe, domain) # <4>
|
||||
async for task in group: # <5>
|
||||
domain, found = task.result
|
||||
mark = '+' if found else ' '
|
||||
print(f'{mark} {domain}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run(main()) # <11>
|
||||
run(main()) # <6>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from curio import TaskGroup
|
||||
from curio.socket import getaddrinfo, gaierror
|
||||
import curio.socket as socket
|
||||
from collections.abc import Iterable, AsyncIterator
|
||||
from typing import NamedTuple
|
||||
|
||||
@@ -9,10 +9,10 @@ class Result(NamedTuple):
|
||||
found: bool
|
||||
|
||||
|
||||
async def probe(domain: str) -> Result:
|
||||
async def probe(domain: str) -> Result:
|
||||
try:
|
||||
await getaddrinfo(domain, None)
|
||||
except gaierror:
|
||||
await socket.getaddrinfo(domain, None)
|
||||
except socket.gaierror:
|
||||
return Result(domain, False)
|
||||
return Result(domain, True)
|
||||
|
||||
|
||||
@@ -4,28 +4,28 @@
|
||||
Class ``InvertedIndex`` builds an inverted index mapping each word to
|
||||
the set of Unicode characters which contain that word in their names.
|
||||
|
||||
Optional arguments to the constructor are ``first`` and ``last+1`` character
|
||||
codes to index, to make testing easier.
|
||||
Optional arguments to the constructor are ``first`` and ``last+1``
|
||||
character codes to index, to make testing easier. In the examples
|
||||
below, only the ASCII range was indexed.
|
||||
|
||||
In the example below, only the ASCII range was indexed::
|
||||
The `entries` attribute is a `defaultdict` with uppercased single
|
||||
words as keys::
|
||||
|
||||
>>> idx = InvertedIndex(32, 128)
|
||||
>>> idx.entries['DOLLAR']
|
||||
{'$'}
|
||||
>>> sorted(idx.entries['SIGN'])
|
||||
['#', '$', '%', '+', '<', '=', '>']
|
||||
>>> sorted(idx.entries['DIGIT'])
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||
>>> idx.entries['DIGIT'] & idx.entries['EIGHT']
|
||||
{'8'}
|
||||
>>> idx.search('digit')
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||
>>> idx.search('eight digit')
|
||||
['8']
|
||||
>>> idx.search('a letter')
|
||||
['A', 'a']
|
||||
>>> idx.search('a letter capital')
|
||||
['A']
|
||||
>>> idx.search('borogove')
|
||||
[]
|
||||
>>> idx.entries['A'] & idx.entries['SMALL']
|
||||
{'a'}
|
||||
>>> idx.entries['BRILLIG']
|
||||
set()
|
||||
|
||||
The `.search()` method takes a string, uppercases it, splits it into
|
||||
words, and returns the intersection of the entries for each word::
|
||||
|
||||
>>> idx.search('capital a')
|
||||
{'A'}
|
||||
|
||||
"""
|
||||
|
||||
@@ -58,17 +58,16 @@ class InvertedIndex:
|
||||
entries[word].add(char)
|
||||
self.entries = entries
|
||||
|
||||
def search(self, query: str) -> list[Char]:
|
||||
def search(self, query: str) -> set[Char]:
|
||||
if words := list(tokenize(query)):
|
||||
first = self.entries[words[0]]
|
||||
result = first.intersection(*(self.entries[w] for w in words[1:]))
|
||||
return sorted(result)
|
||||
found = self.entries[words[0]]
|
||||
return found.intersection(*(self.entries[w] for w in words[1:]))
|
||||
else:
|
||||
return []
|
||||
return set()
|
||||
|
||||
|
||||
def format_results(chars: list[Char]) -> Iterator[str]:
|
||||
for char in chars:
|
||||
def format_results(chars: set[Char]) -> Iterator[str]:
|
||||
for char in sorted(chars):
|
||||
name = unicodedata.name(char)
|
||||
code = ord(char)
|
||||
yield f'U+{code:04X}\t{char}\t{name}'
|
||||
@@ -77,7 +76,7 @@ def format_results(chars: list[Char]) -> Iterator[str]:
|
||||
def main(words: list[str]) -> None:
|
||||
if not words:
|
||||
print('Please give one or more words to search.')
|
||||
sys.exit()
|
||||
sys.exit(2) # command line usage error
|
||||
index = InvertedIndex()
|
||||
chars = index.search(' '.join(words))
|
||||
for line in format_results(chars):
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
const input = document.getElementById('query');
|
||||
input.addEventListener('change', updateTable);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -18,19 +18,19 @@ class CharName(BaseModel): # <2>
|
||||
|
||||
def init(app): # <3>
|
||||
app.state.index = InvertedIndex()
|
||||
static = pathlib.Path(__file__).parent.absolute() / 'static'
|
||||
static = pathlib.Path(__file__).parent.absolute() / 'static' # <4>
|
||||
with open(static / 'form.html') as fp:
|
||||
app.state.form = fp.read()
|
||||
|
||||
init(app) # <4>
|
||||
init(app) # <5>
|
||||
|
||||
@app.get('/search', response_model=list[CharName]) # <5>
|
||||
async def search(q: str): # <6>
|
||||
@app.get('/search', response_model=list[CharName]) # <6>
|
||||
async def search(q: str): # <7>
|
||||
chars = app.state.index.search(q)
|
||||
return ({'char': c, 'name': name(c)} for c in chars) # <7>
|
||||
return ({'char': c, 'name': name(c)} for c in chars) # <8>
|
||||
|
||||
@app.get('/', # <8>
|
||||
response_class=HTMLResponse,
|
||||
include_in_schema=False)
|
||||
def form():
|
||||
@app.get('/', response_class=HTMLResponse, include_in_schema=False)
|
||||
def form(): # <9>
|
||||
return app.state.form
|
||||
|
||||
# no main funcion # <10>
|
||||
|
||||
Reference in New Issue
Block a user