From d7e37ad0fdff8b5fc65e11414a353a6f3b407925 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Sun, 5 Mar 2017 14:22:20 -0300
Subject: [PATCH 01/24] updated threading example to use Event object instead
of user defined Signal instance
---
18-asyncio/spinner_thread.py | 15 +++++----------
18b-async-await/spinner_thread.py | 15 +++++----------
2 files changed, 10 insertions(+), 20 deletions(-)
diff --git a/18-asyncio/spinner_thread.py b/18-asyncio/spinner_thread.py
index 23feb8f..a907a25 100644
--- a/18-asyncio/spinner_thread.py
+++ b/18-asyncio/spinner_thread.py
@@ -11,19 +11,14 @@
import sys
-class Signal: # <1>
- go = True
-
-
-def spin(msg, signal): # <2>
+def spin(msg, done): # <2>
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'): # <3>
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status)) # <4>
- time.sleep(.1)
- if not signal.go: # <5>
+ if done.wait(.1): # <5>
break
write(' ' * len(status) + '\x08' * len(status)) # <6>
@@ -35,13 +30,13 @@ def slow_function(): # <7>
def supervisor(): # <9>
- signal = Signal()
+ done = threading.Event()
spinner = threading.Thread(target=spin,
- args=('thinking!', signal))
+ args=('thinking!', done))
print('spinner object:', spinner) # <10>
spinner.start() # <11>
result = slow_function() # <12>
- signal.go = False # <13>
+ done.set() # <13>
spinner.join() # <14>
return result
diff --git a/18b-async-await/spinner_thread.py b/18b-async-await/spinner_thread.py
index 23feb8f..a907a25 100644
--- a/18b-async-await/spinner_thread.py
+++ b/18b-async-await/spinner_thread.py
@@ -11,19 +11,14 @@
import sys
-class Signal: # <1>
- go = True
-
-
-def spin(msg, signal): # <2>
+def spin(msg, done): # <2>
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'): # <3>
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status)) # <4>
- time.sleep(.1)
- if not signal.go: # <5>
+ if done.wait(.1): # <5>
break
write(' ' * len(status) + '\x08' * len(status)) # <6>
@@ -35,13 +30,13 @@ def slow_function(): # <7>
def supervisor(): # <9>
- signal = Signal()
+ done = threading.Event()
spinner = threading.Thread(target=spin,
- args=('thinking!', signal))
+ args=('thinking!', done))
print('spinner object:', spinner) # <10>
spinner.start() # <11>
result = slow_function() # <12>
- signal.go = False # <13>
+ done.set() # <13>
spinner.join() # <14>
return result
From 8474e2d12bfa26ae23c6f9ef2f0eb72c4142c341 Mon Sep 17 00:00:00 2001
From: Egor Poderyagin
Date: Tue, 6 Jun 2017 06:30:29 +0300
Subject: [PATCH 02/24] remove unnecessary import
---
16-coroutine/taxi_sim.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/16-coroutine/taxi_sim.py b/16-coroutine/taxi_sim.py
index 705418a..e9c4cc1 100644
--- a/16-coroutine/taxi_sim.py
+++ b/16-coroutine/taxi_sim.py
@@ -53,7 +53,6 @@
import collections
import queue
import argparse
-import time
DEFAULT_NUMBER_OF_TAXIS = 3
DEFAULT_END_TIME = 180
From 8bbaeb1e36b4714b46aa9604ec8d84f520e84cf5 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 6 Jul 2017 11:38:22 -0300
Subject: [PATCH 03/24] spinner example ported to async/await and curio
---
.gitignore | 1 +
18-asyncio/spinner_asyncio.py | 2 ++
18-asyncio/spinner_await.py | 50 +++++++++++++++++++++++++++++++++++
18-asyncio/spinner_curio.py | 49 ++++++++++++++++++++++++++++++++++
18-asyncio/spinner_thread.py | 2 ++
5 files changed, 104 insertions(+)
mode change 100644 => 100755 18-asyncio/spinner_asyncio.py
create mode 100755 18-asyncio/spinner_await.py
create mode 100755 18-asyncio/spinner_curio.py
mode change 100644 => 100755 18-asyncio/spinner_thread.py
diff --git a/.gitignore b/.gitignore
index 1047be4..759a60f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
*.sublime-project
*.sublime-workspace
+.env*
concurrency/flags/img/*.gif
concurrency/charfinder/charfinder_index.pickle
18-asyncio/charfinder/charfinder_index.pickle
diff --git a/18-asyncio/spinner_asyncio.py b/18-asyncio/spinner_asyncio.py
old mode 100644
new mode 100755
index 3366998..aeb2b55
--- a/18-asyncio/spinner_asyncio.py
+++ b/18-asyncio/spinner_asyncio.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
# spinner_asyncio.py
# credits: Example by Luciano Ramalho inspired by
diff --git a/18-asyncio/spinner_await.py b/18-asyncio/spinner_await.py
new file mode 100755
index 0000000..ad9c9b0
--- /dev/null
+++ b/18-asyncio/spinner_await.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+
+# spinner_await.py
+
+# credits: Example by Luciano Ramalho inspired by
+# Michele Simionato's multiprocessing example in the python-list:
+# https://mail.python.org/pipermail/python-list/2009-February/538048.html
+
+import asyncio
+import itertools
+import sys
+
+
+async def spin(msg): # <1>
+ write, flush = sys.stdout.write, sys.stdout.flush
+ for char in itertools.cycle('|/-\\'):
+ status = char + ' ' + msg
+ write(status)
+ flush()
+ write('\x08' * len(status))
+ try:
+ await asyncio.sleep(.1) # <2>
+ except asyncio.CancelledError: # <3>
+ break
+ write(' ' * len(status) + '\x08' * len(status))
+
+
+async def slow_function(): # <4>
+ # pretend waiting a long time for I/O
+ await asyncio.sleep(3) # <5>
+ return 42
+
+
+async def supervisor(): # <6>
+ spinner = asyncio.ensure_future(spin('thinking!')) # <7>
+ print('spinner object:', spinner) # <8>
+ result = await slow_function() # <9>
+ spinner.cancel() # <10>
+ return result
+
+
+def main():
+ loop = asyncio.get_event_loop() # <11>
+ result = loop.run_until_complete(supervisor()) # <12>
+ loop.close()
+ print('Answer:', result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/18-asyncio/spinner_curio.py b/18-asyncio/spinner_curio.py
new file mode 100755
index 0000000..9475b7c
--- /dev/null
+++ b/18-asyncio/spinner_curio.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+# spinner_curio.py
+
+# credits: Example by Luciano Ramalho inspired by
+# Michele Simionato's multiprocessing example in the python-list:
+# https://mail.python.org/pipermail/python-list/2009-February/538048.html
+
+import curio
+
+import itertools
+import sys
+
+
+async def spin(msg): # <1>
+ write, flush = sys.stdout.write, sys.stdout.flush
+ for char in itertools.cycle('|/-\\'):
+ status = char + ' ' + msg
+ write(status)
+ flush()
+ write('\x08' * len(status))
+ try:
+ await curio.sleep(.1) # <2>
+ except curio.CancelledError: # <3>
+ break
+ write(' ' * len(status) + '\x08' * len(status))
+
+
+async def slow_function(): # <4>
+ # pretend waiting a long time for I/O
+ await curio.sleep(3) # <5>
+ return 42
+
+
+async def supervisor(): # <6>
+ spinner = await curio.spawn(spin('thinking!')) # <7>
+ print('spinner object:\n ', repr(spinner)) # <8>
+ result = await slow_function() # <9>
+ await spinner.cancel() # <10>
+ return result
+
+
+def main():
+ result = curio.run(supervisor) # <12>
+ print('Answer:', result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/18-asyncio/spinner_thread.py b/18-asyncio/spinner_thread.py
old mode 100644
new mode 100755
index a907a25..dffcca6
--- a/18-asyncio/spinner_thread.py
+++ b/18-asyncio/spinner_thread.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
# spinner_thread.py
# credits: Adapted from Michele Simionato's
From 2832de13e2d4b5d39a0370add4965e6151d87c78 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 6 Jul 2017 13:29:21 -0300
Subject: [PATCH 04/24] spinner curio example in chapter 18b
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 759a60f..026ccfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.sublime-project
*.sublime-workspace
.env*
+.DS_Store
concurrency/flags/img/*.gif
concurrency/charfinder/charfinder_index.pickle
18-asyncio/charfinder/charfinder_index.pickle
From f6a171f217531a21cbe72e8dabc4b88157dafd6b Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 6 Jul 2017 13:31:55 -0300
Subject: [PATCH 05/24] spinner curio example in chapter 18b
---
18b-async-await/README.rst | 2 +-
18b-async-await/spinner_curio.py | 49 ++++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+), 1 deletion(-)
create mode 100755 18b-async-await/spinner_curio.py
diff --git a/18b-async-await/README.rst b/18b-async-await/README.rst
index 0a5d0ca..0f4f1b8 100644
--- a/18b-async-await/README.rst
+++ b/18b-async-await/README.rst
@@ -1,4 +1,4 @@
-Sample code for Chapter 18 - "Concurrency with asyncio"
+Refactored sample code for Chapter 18 - "Concurrency with asyncio"
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
http://shop.oreilly.com/product/0636920032519.do
diff --git a/18b-async-await/spinner_curio.py b/18b-async-await/spinner_curio.py
new file mode 100755
index 0000000..9475b7c
--- /dev/null
+++ b/18b-async-await/spinner_curio.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+# spinner_curio.py
+
+# credits: Example by Luciano Ramalho inspired by
+# Michele Simionato's multiprocessing example in the python-list:
+# https://mail.python.org/pipermail/python-list/2009-February/538048.html
+
+import curio
+
+import itertools
+import sys
+
+
+async def spin(msg): # <1>
+ write, flush = sys.stdout.write, sys.stdout.flush
+ for char in itertools.cycle('|/-\\'):
+ status = char + ' ' + msg
+ write(status)
+ flush()
+ write('\x08' * len(status))
+ try:
+ await curio.sleep(.1) # <2>
+ except curio.CancelledError: # <3>
+ break
+ write(' ' * len(status) + '\x08' * len(status))
+
+
+async def slow_function(): # <4>
+ # pretend waiting a long time for I/O
+ await curio.sleep(3) # <5>
+ return 42
+
+
+async def supervisor(): # <6>
+ spinner = await curio.spawn(spin('thinking!')) # <7>
+ print('spinner object:\n ', repr(spinner)) # <8>
+ result = await slow_function() # <9>
+ await spinner.cancel() # <10>
+ return result
+
+
+def main():
+ result = curio.run(supervisor) # <12>
+ print('Answer:', result)
+
+
+if __name__ == '__main__':
+ main()
From de8ae1fc7256aaade8f51a9df30133700b4b8548 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 6 Jul 2017 13:34:40 -0300
Subject: [PATCH 06/24] spinner curio example in chapter 18b
---
18b-async-await/spinner_curio.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/18b-async-await/spinner_curio.py b/18b-async-await/spinner_curio.py
index 9475b7c..e2b1d32 100755
--- a/18b-async-await/spinner_curio.py
+++ b/18b-async-await/spinner_curio.py
@@ -2,12 +2,14 @@
# spinner_curio.py
-# credits: Example by Luciano Ramalho inspired by
+# credits:
+# Example from the book Fluent Python by Luciano Ramalho inspired by
# Michele Simionato's multiprocessing example in the python-list:
# https://mail.python.org/pipermail/python-list/2009-February/538048.html
+# using David Beazley's `curio` library:
+# https://github.com/dabeaz/curio
import curio
-
import itertools
import sys
From c2347c5eb45f8718ee2d796b4832349b4a528db5 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 6 Jul 2017 16:10:53 -0300
Subject: [PATCH 07/24] spinner curio example in chapter 18b
---
18b-async-await/spinner_curio.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/18b-async-await/spinner_curio.py b/18b-async-await/spinner_curio.py
index e2b1d32..169a941 100755
--- a/18b-async-await/spinner_curio.py
+++ b/18b-async-await/spinner_curio.py
@@ -43,7 +43,7 @@ async def supervisor(): # <6>
def main():
- result = curio.run(supervisor) # <12>
+ result = curio.run(supervisor) # <11>
print('Answer:', result)
From 3d74f0e18344c2d257e00b689f4c7fe25b974952 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 6 Jul 2017 16:22:39 -0300
Subject: [PATCH 08/24] spinner curio example in chapter 18b
---
18b-async-await/spinner_curio.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/18b-async-await/spinner_curio.py b/18b-async-await/spinner_curio.py
index 169a941..0af06d8 100755
--- a/18b-async-await/spinner_curio.py
+++ b/18b-async-await/spinner_curio.py
@@ -6,7 +6,7 @@
# Example from the book Fluent Python by Luciano Ramalho inspired by
# Michele Simionato's multiprocessing example in the python-list:
# https://mail.python.org/pipermail/python-list/2009-February/538048.html
-# using David Beazley's `curio` library:
+# ported to use David Beazley's `curio` library:
# https://github.com/dabeaz/curio
import curio
From 17dac42139a0688f62108b88c216c6b48f5acba5 Mon Sep 17 00:00:00 2001
From: andela-cdike
Date: Sun, 27 Aug 2017 20:30:49 +0100
Subject: [PATCH 09/24] Tests should use separate shelve test database - Switch
from depreccated yield_fixture to fixture decorator
---
19-dyn-attr-prop/oscon/test_schedule1.py | 9 +++++++--
19-dyn-attr-prop/oscon/test_schedule2.py | 7 +++++--
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/19-dyn-attr-prop/oscon/test_schedule1.py b/19-dyn-attr-prop/oscon/test_schedule1.py
index dbaacc9..ba5dfd1 100644
--- a/19-dyn-attr-prop/oscon/test_schedule1.py
+++ b/19-dyn-attr-prop/oscon/test_schedule1.py
@@ -1,15 +1,20 @@
+import os
import shelve
+
import pytest
import schedule1 as schedule
+DB_NAME = 'data/test_db'
+
-@pytest.yield_fixture
+@pytest.fixture(scope='module')
def db():
- with shelve.open(schedule.DB_NAME) as the_db:
+ with shelve.open(DB_NAME) as the_db:
if schedule.CONFERENCE not in the_db:
schedule.load_db(the_db)
yield the_db
+ os.remove(DB_NAME)
def test_record_class():
diff --git a/19-dyn-attr-prop/oscon/test_schedule2.py b/19-dyn-attr-prop/oscon/test_schedule2.py
index de09d32..ab1c79c 100644
--- a/19-dyn-attr-prop/oscon/test_schedule2.py
+++ b/19-dyn-attr-prop/oscon/test_schedule2.py
@@ -1,15 +1,18 @@
+import os
import shelve
+
import pytest
import schedule2 as schedule
-@pytest.yield_fixture
+@pytest.fixture(scope='module')
def db():
- with shelve.open(schedule.DB_NAME) as the_db:
+ with shelve.open(DB_NAME) as the_db:
if schedule.CONFERENCE not in the_db:
schedule.load_db(the_db)
yield the_db
+ os.remove(DB_NAME)
def test_record_attr_access():
From 3dd11744d1c0b1f00860e985ee2a0761e73ef7e7 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Tue, 22 Jan 2019 17:27:43 -0200
Subject: [PATCH 10/24] updated requests to avoid CVE-2018-18074
---
17-futures/countries/requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/17-futures/countries/requirements.txt b/17-futures/countries/requirements.txt
index c6462ae..6f29576 100644
--- a/17-futures/countries/requirements.txt
+++ b/17-futures/countries/requirements.txt
@@ -1,3 +1,3 @@
aiohttp==0.13.1
-requests==2.5.1
+requests==2.21.0
tqdm==1.0
From eeb6b3d4985eadb574ef59900a35f2e5d2952b7d Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Wed, 23 Jan 2019 13:51:14 -0200
Subject: [PATCH 11/24] some chapter 17 examples updated for Python 3.7
---
.gitignore | 2 +
17-futures-py3.7/README.rst | 10 +++
17-futures-py3.7/countries/flags.py | 63 ++++++++++++++++
17-futures-py3.7/countries/flags_asyncio.py | 72 +++++++++++++++++++
.../countries/flags_threadpool.py | 71 ++++++++++++++++++
17-futures-py3.7/countries/requirements.txt | 2 +
17-futures-py3.7/demo_executor_map.py | 34 +++++++++
7 files changed, 254 insertions(+)
create mode 100644 17-futures-py3.7/README.rst
create mode 100644 17-futures-py3.7/countries/flags.py
create mode 100644 17-futures-py3.7/countries/flags_asyncio.py
create mode 100644 17-futures-py3.7/countries/flags_threadpool.py
create mode 100644 17-futures-py3.7/countries/requirements.txt
create mode 100644 17-futures-py3.7/demo_executor_map.py
diff --git a/.gitignore b/.gitignore
index 026ccfb..e833757 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.vscode/
+.venv3?/
*.sublime-project
*.sublime-workspace
.env*
diff --git a/17-futures-py3.7/README.rst b/17-futures-py3.7/README.rst
new file mode 100644
index 0000000..7167b33
--- /dev/null
+++ b/17-futures-py3.7/README.rst
@@ -0,0 +1,10 @@
+Updated sample code for Chapter 17 - "Concurrency with futures"
+
+From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
+http://shop.oreilly.com/product/0636920032519.do
+
+ This directory contains code updated to run with Python 3.7 and
+ **aiohttp** 3.5. When the first edition of "Fluent Python" was
+ written, the **asyncio** package was provisional, and the latest
+ version of **aiohttp** was 0.13.1. The API for both packages had
+ significant breaking changes.
diff --git a/17-futures-py3.7/countries/flags.py b/17-futures-py3.7/countries/flags.py
new file mode 100644
index 0000000..7a7f854
--- /dev/null
+++ b/17-futures-py3.7/countries/flags.py
@@ -0,0 +1,63 @@
+"""Download flags of top 20 countries by population
+
+Sequential version
+
+Sample run::
+
+ $ python3 flags.py
+ BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN
+ 20 flags downloaded in 5.49s
+
+"""
+# BEGIN FLAGS_PY
+import os
+import time
+import sys
+
+import requests # <1>
+
+POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
+ 'MX PH VN ET EG DE IR TR CD FR').split() # <2>
+
+BASE_URL = 'http://flupy.org/data/flags' # <3>
+
+DEST_DIR = 'downloads/' # <4>
+
+
+def save_flag(img, filename): # <5>
+ path = os.path.join(DEST_DIR, filename)
+ with open(path, 'wb') as fp:
+ fp.write(img)
+
+
+def get_flag(cc): # <6>
+ url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
+ resp = requests.get(url)
+ return resp.content
+
+
+def show(text): # <7>
+ print(text, end=' ')
+ sys.stdout.flush()
+
+
+def download_many(cc_list): # <8>
+ for cc in sorted(cc_list): # <9>
+ image = get_flag(cc)
+ show(cc)
+ save_flag(image, cc.lower() + '.gif')
+
+ return len(cc_list)
+
+
+def main(): # <10>
+ t0 = time.time()
+ count = download_many(POP20_CC)
+ elapsed = time.time() - t0
+ msg = '\n{} flags downloaded in {:.2f}s'
+ print(msg.format(count, elapsed))
+
+
+if __name__ == '__main__':
+ main()
+# END FLAGS_PY
diff --git a/17-futures-py3.7/countries/flags_asyncio.py b/17-futures-py3.7/countries/flags_asyncio.py
new file mode 100644
index 0000000..89421f8
--- /dev/null
+++ b/17-futures-py3.7/countries/flags_asyncio.py
@@ -0,0 +1,72 @@
+"""Download flags of top 20 countries by population
+
+asyncio + aiottp version
+
+Sample run::
+
+ $ python3 flags_asyncio.py
+ CN EG BR IN ID RU NG VN JP DE TR PK FR ET MX PH US IR CD BD
+ 20 flags downloaded in 0.35s
+
+"""
+# BEGIN FLAGS_ASYNCIO
+import os
+import time
+import sys
+import asyncio # <1>
+
+import aiohttp # <2>
+
+
+POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
+ 'MX PH VN ET EG DE IR TR CD FR').split()
+
+BASE_URL = 'http://flupy.org/data/flags'
+
+DEST_DIR = 'downloads/'
+
+
+def save_flag(img, filename):
+ path = os.path.join(DEST_DIR, filename)
+ with open(path, 'wb') as fp:
+ fp.write(img)
+
+
+async def get_flag(session, cc): # <3>
+ url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
+ async with session.get(url) as resp: # <4>
+ return await resp.read() # <5>
+
+
+def show(text):
+ print(text, end=' ')
+ sys.stdout.flush()
+
+
+async def download_one(session, cc): # <6>
+ image = await get_flag(session, cc) # <7>
+ show(cc)
+ save_flag(image, cc.lower() + '.gif')
+ return cc
+
+
+async def download_many(cc_list):
+ async with aiohttp.ClientSession() as session: # <8>
+ res = await asyncio.gather( # <9>
+ *[asyncio.create_task(download_one(session, cc))
+ for cc in sorted(cc_list)])
+
+ return len(res)
+
+
+def main(): # <10>
+ t0 = time.time()
+ count = asyncio.run(download_many(POP20_CC))
+ elapsed = time.time() - t0
+ msg = '\n{} flags downloaded in {:.2f}s'
+ print(msg.format(count, elapsed))
+
+
+if __name__ == '__main__':
+ main()
+# END FLAGS_ASYNCIO
diff --git a/17-futures-py3.7/countries/flags_threadpool.py b/17-futures-py3.7/countries/flags_threadpool.py
new file mode 100644
index 0000000..47a5ee6
--- /dev/null
+++ b/17-futures-py3.7/countries/flags_threadpool.py
@@ -0,0 +1,71 @@
+"""Download flags of top 20 countries by population
+
+ThreadPoolExecutor version
+
+Sample run::
+
+ $ python3 flags_threadpool.py
+ DE FR BD CN EG RU IN TR VN ID JP BR NG MX PK ET PH CD US IR
+ 20 flags downloaded in 0.35s
+
+"""
+# BEGIN FLAGS_THREADPOOL
+import os
+import time
+import sys
+from concurrent import futures # <1>
+
+import requests
+
+POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
+ 'MX PH VN ET EG DE IR TR CD FR').split()
+
+BASE_URL = 'http://flupy.org/data/flags'
+
+DEST_DIR = 'downloads/'
+
+MAX_WORKERS = 20 # <2>
+
+def save_flag(img, filename):
+ path = os.path.join(DEST_DIR, filename)
+ with open(path, 'wb') as fp:
+ fp.write(img)
+
+
+def get_flag(cc):
+ url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
+ resp = requests.get(url)
+ return resp.content
+
+
+def show(text):
+ print(text, end=' ')
+ sys.stdout.flush()
+
+
+def download_one(cc): # <3>
+ image = get_flag(cc)
+ show(cc)
+ save_flag(image, cc.lower() + '.gif')
+ return cc
+
+
+def download_many(cc_list):
+ workers = min(MAX_WORKERS, len(cc_list)) # <4>
+ with futures.ThreadPoolExecutor(workers) as executor: # <5>
+ res = executor.map(download_one, sorted(cc_list)) # <6>
+
+ return len(list(res)) # <7>
+
+
+def main(): # <10>
+ t0 = time.time()
+ count = download_many(POP20_CC)
+ elapsed = time.time() - t0
+ msg = '\n{} flags downloaded in {:.2f}s'
+ print(msg.format(count, elapsed))
+
+
+if __name__ == '__main__':
+ main()
+# END FLAGS_THREADPOOL
diff --git a/17-futures-py3.7/countries/requirements.txt b/17-futures-py3.7/countries/requirements.txt
new file mode 100644
index 0000000..aa7a6de
--- /dev/null
+++ b/17-futures-py3.7/countries/requirements.txt
@@ -0,0 +1,2 @@
+requests==2.21.0
+aiohttp==3.5.4
diff --git a/17-futures-py3.7/demo_executor_map.py b/17-futures-py3.7/demo_executor_map.py
new file mode 100644
index 0000000..f3625cf
--- /dev/null
+++ b/17-futures-py3.7/demo_executor_map.py
@@ -0,0 +1,34 @@
+"""
+Experiment with ``ThreadPoolExecutor.map``
+"""
+# BEGIN EXECUTOR_MAP
+from time import sleep, strftime
+from concurrent import futures
+
+
+def display(*args): # <1>
+ print(strftime('[%H:%M:%S]'), end=' ')
+ print(*args)
+
+
+def loiter(n): # <2>
+ msg = '{}loiter({}): doing nothing for {}s...'
+ display(msg.format('\t'*n, n, n))
+ sleep(n)
+ msg = '{}loiter({}): done.'
+ display(msg.format('\t'*n, n))
+ return n * 10 # <3>
+
+
+def main():
+ display('Script starting.')
+ executor = futures.ThreadPoolExecutor(max_workers=3) # <4>
+ results = executor.map(loiter, range(5)) # <5>
+ display('results:', results) # <6>.
+ display('Waiting for individual results:')
+ for i, result in enumerate(results): # <7>
+ display('result {}: {}'.format(i, result))
+
+
+main()
+# END EXECUTOR_MAP
From 29d6835f3b06ed9e125cb752f8ef2d407c2afb3f Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Wed, 23 Jan 2019 15:04:03 -0200
Subject: [PATCH 12/24] updated some examples from ch. 17 e 18 to Python 3.7
---
.../{flags_asyncio.py => asyncio_flags.py} | 0
...lags_threadpool.py => threadpool_flags.py} | 0
.../README.rst | 0
.../charfinder/.gitignore | 0
.../charfinder/charfinder.py | 0
.../charfinder/tcp_charfinder.py | 0
.../charfinder/test_charfinder.py | 0
.../countries/README.rst | 0
.../spinner_asyncio.py | 6 +-
.../spinner_curio.py | 0
.../spinner_thread.py | 0
.../charfinder/http_charfinder.html | 19 -----
18b-async-await/charfinder/http_charfinder.py | 71 -------------------
18b-async-await/spinner_asyncio.py | 53 --------------
14 files changed, 2 insertions(+), 147 deletions(-)
rename 17-futures-py3.7/countries/{flags_asyncio.py => asyncio_flags.py} (100%)
rename 17-futures-py3.7/countries/{flags_threadpool.py => threadpool_flags.py} (100%)
rename {18b-async-await => 18-asyncio-py3.7}/README.rst (100%)
rename {18b-async-await => 18-asyncio-py3.7}/charfinder/.gitignore (100%)
rename {18b-async-await => 18-asyncio-py3.7}/charfinder/charfinder.py (100%)
rename {18b-async-await => 18-asyncio-py3.7}/charfinder/tcp_charfinder.py (100%)
rename {18b-async-await => 18-asyncio-py3.7}/charfinder/test_charfinder.py (100%)
rename {18b-async-await => 18-asyncio-py3.7}/countries/README.rst (100%)
rename 18b-async-await/spinner_await.py => 18-asyncio-py3.7/spinner_asyncio.py (85%)
rename {18b-async-await => 18-asyncio-py3.7}/spinner_curio.py (100%)
rename {18b-async-await => 18-asyncio-py3.7}/spinner_thread.py (100%)
delete mode 100644 18b-async-await/charfinder/http_charfinder.html
delete mode 100755 18b-async-await/charfinder/http_charfinder.py
delete mode 100644 18b-async-await/spinner_asyncio.py
diff --git a/17-futures-py3.7/countries/flags_asyncio.py b/17-futures-py3.7/countries/asyncio_flags.py
similarity index 100%
rename from 17-futures-py3.7/countries/flags_asyncio.py
rename to 17-futures-py3.7/countries/asyncio_flags.py
diff --git a/17-futures-py3.7/countries/flags_threadpool.py b/17-futures-py3.7/countries/threadpool_flags.py
similarity index 100%
rename from 17-futures-py3.7/countries/flags_threadpool.py
rename to 17-futures-py3.7/countries/threadpool_flags.py
diff --git a/18b-async-await/README.rst b/18-asyncio-py3.7/README.rst
similarity index 100%
rename from 18b-async-await/README.rst
rename to 18-asyncio-py3.7/README.rst
diff --git a/18b-async-await/charfinder/.gitignore b/18-asyncio-py3.7/charfinder/.gitignore
similarity index 100%
rename from 18b-async-await/charfinder/.gitignore
rename to 18-asyncio-py3.7/charfinder/.gitignore
diff --git a/18b-async-await/charfinder/charfinder.py b/18-asyncio-py3.7/charfinder/charfinder.py
similarity index 100%
rename from 18b-async-await/charfinder/charfinder.py
rename to 18-asyncio-py3.7/charfinder/charfinder.py
diff --git a/18b-async-await/charfinder/tcp_charfinder.py b/18-asyncio-py3.7/charfinder/tcp_charfinder.py
similarity index 100%
rename from 18b-async-await/charfinder/tcp_charfinder.py
rename to 18-asyncio-py3.7/charfinder/tcp_charfinder.py
diff --git a/18b-async-await/charfinder/test_charfinder.py b/18-asyncio-py3.7/charfinder/test_charfinder.py
similarity index 100%
rename from 18b-async-await/charfinder/test_charfinder.py
rename to 18-asyncio-py3.7/charfinder/test_charfinder.py
diff --git a/18b-async-await/countries/README.rst b/18-asyncio-py3.7/countries/README.rst
similarity index 100%
rename from 18b-async-await/countries/README.rst
rename to 18-asyncio-py3.7/countries/README.rst
diff --git a/18b-async-await/spinner_await.py b/18-asyncio-py3.7/spinner_asyncio.py
similarity index 85%
rename from 18b-async-await/spinner_await.py
rename to 18-asyncio-py3.7/spinner_asyncio.py
index f9a3dfc..b9fc9d5 100644
--- a/18b-async-await/spinner_await.py
+++ b/18-asyncio-py3.7/spinner_asyncio.py
@@ -31,7 +31,7 @@ async def slow_function(): # <5>
async def supervisor(): # <7>
- spinner = asyncio.ensure_future(spin('thinking!')) # <8>
+ spinner = asyncio.create_task(spin('thinking!')) # <8>
print('spinner object:', spinner) # <9>
result = await slow_function() # <10>
spinner.cancel() # <11>
@@ -39,9 +39,7 @@ async def supervisor(): # <7>
def main():
- loop = asyncio.get_event_loop() # <12>
- result = loop.run_until_complete(supervisor()) # <13>
- loop.close()
+ result = asyncio.run(supervisor()) # <12>
print('Answer:', result)
diff --git a/18b-async-await/spinner_curio.py b/18-asyncio-py3.7/spinner_curio.py
similarity index 100%
rename from 18b-async-await/spinner_curio.py
rename to 18-asyncio-py3.7/spinner_curio.py
diff --git a/18b-async-await/spinner_thread.py b/18-asyncio-py3.7/spinner_thread.py
similarity index 100%
rename from 18b-async-await/spinner_thread.py
rename to 18-asyncio-py3.7/spinner_thread.py
diff --git a/18b-async-await/charfinder/http_charfinder.html b/18b-async-await/charfinder/http_charfinder.html
deleted file mode 100644
index 43b9cd7..0000000
--- a/18b-async-await/charfinder/http_charfinder.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
- Charfinder
-
-
- Examples: {links}
-
-
-
-
-
-
diff --git a/18b-async-await/charfinder/http_charfinder.py b/18b-async-await/charfinder/http_charfinder.py
deleted file mode 100755
index ace15c5..0000000
--- a/18b-async-await/charfinder/http_charfinder.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import asyncio
-from aiohttp import web
-
-from charfinder import UnicodeNameIndex
-
-TEMPLATE_NAME = 'http_charfinder.html'
-CONTENT_TYPE = 'text/html; charset=UTF-8'
-SAMPLE_WORDS = ('bismillah chess cat circled Malayalam digit'
- ' Roman face Ethiopic black mark symbol dot'
- ' operator Braille hexagram').split()
-
-ROW_TPL = '{code_str} | {char} | {name} |
'
-LINK_TPL = '{0}'
-LINKS_HTML = ', '.join(LINK_TPL.format(word) for word in
- sorted(SAMPLE_WORDS, key=str.upper))
-
-
-index = UnicodeNameIndex()
-with open(TEMPLATE_NAME) as tpl:
- template = tpl.read()
-template = template.replace('{links}', LINKS_HTML)
-
-# BEGIN HTTP_CHARFINDER_HOME
-def home(request): # <1>
- query = request.GET.get('query', '').strip() # <2>
- print('Query: {!r}'.format(query)) # <3>
- if query: # <4>
- descriptions = list(index.find_descriptions(query))
- res = '\n'.join(ROW_TPL.format(**vars(descr))
- for descr in descriptions)
- msg = index.status(query, len(descriptions))
- else:
- descriptions = []
- res = ''
- msg = 'Enter words describing characters.'
-
- html = template.format(query=query, result=res, # <5>
- message=msg)
- print('Sending {} results'.format(len(descriptions))) # <6>
- return web.Response(content_type=CONTENT_TYPE, text=html) # <7>
-# END HTTP_CHARFINDER_HOME
-
-
-# BEGIN HTTP_CHARFINDER_SETUP
-async def init(loop, address, port): # <1>
- app = web.Application(loop=loop) # <2>
- app.router.add_route('GET', '/', home) # <3>
- handler = app.make_handler() # <4>
- server = await loop.create_server(handler,
- address, port) # <5>
- return server.sockets[0].getsockname() # <6>
-
-def main(address="127.0.0.1", port=8888):
- port = int(port)
- loop = asyncio.get_event_loop()
- host = loop.run_until_complete(init(loop, address, port)) # <7>
- print('Serving on {}. Hit CTRL-C to stop.'.format(host))
- try:
- loop.run_forever() # <8>
- except KeyboardInterrupt: # CTRL+C pressed
- pass
- print('Server shutting down.')
- loop.close() # <9>
-
-
-if __name__ == '__main__':
- main(*sys.argv[1:])
-# END HTTP_CHARFINDER_SETUP
diff --git a/18b-async-await/spinner_asyncio.py b/18b-async-await/spinner_asyncio.py
deleted file mode 100644
index 3366998..0000000
--- a/18b-async-await/spinner_asyncio.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# spinner_asyncio.py
-
-# credits: Example by Luciano Ramalho inspired by
-# Michele Simionato's multiprocessing example in the python-list:
-# https://mail.python.org/pipermail/python-list/2009-February/538048.html
-
-# BEGIN SPINNER_ASYNCIO
-import asyncio
-import itertools
-import sys
-
-
-@asyncio.coroutine # <1>
-def spin(msg): # <2>
- write, flush = sys.stdout.write, sys.stdout.flush
- for char in itertools.cycle('|/-\\'):
- status = char + ' ' + msg
- write(status)
- flush()
- write('\x08' * len(status))
- try:
- yield from asyncio.sleep(.1) # <3>
- except asyncio.CancelledError: # <4>
- break
- write(' ' * len(status) + '\x08' * len(status))
-
-
-@asyncio.coroutine
-def slow_function(): # <5>
- # pretend waiting a long time for I/O
- yield from asyncio.sleep(3) # <6>
- return 42
-
-
-@asyncio.coroutine
-def supervisor(): # <7>
- spinner = asyncio.async(spin('thinking!')) # <8>
- print('spinner object:', spinner) # <9>
- result = yield from slow_function() # <10>
- spinner.cancel() # <11>
- return result
-
-
-def main():
- loop = asyncio.get_event_loop() # <12>
- result = loop.run_until_complete(supervisor()) # <13>
- loop.close()
- print('Answer:', result)
-
-
-if __name__ == '__main__':
- main()
-# END SPINNER_ASYNCIO
From 176dc38c4236dcc23c5b9a7d2866524717a4f444 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Wed, 23 Jan 2019 20:45:59 -0200
Subject: [PATCH 13/24] updated more ch. 18 examples to Python 3.7
---
18-asyncio-py3.7/countdown.py | 30 +++++++++++++++++
18-asyncio-py3.7/spinner_asyncio.py | 28 ++++++++--------
18-asyncio-py3.7/spinner_curio.py | 51 -----------------------------
18-asyncio-py3.7/spinner_thread.py | 2 ++
4 files changed, 47 insertions(+), 64 deletions(-)
create mode 100644 18-asyncio-py3.7/countdown.py
mode change 100644 => 100755 18-asyncio-py3.7/spinner_asyncio.py
delete mode 100755 18-asyncio-py3.7/spinner_curio.py
mode change 100644 => 100755 18-asyncio-py3.7/spinner_thread.py
diff --git a/18-asyncio-py3.7/countdown.py b/18-asyncio-py3.7/countdown.py
new file mode 100644
index 0000000..5f09000
--- /dev/null
+++ b/18-asyncio-py3.7/countdown.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+# Inspired by
+# https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/
+
+import asyncio
+import time
+
+
+async def countdown(label, delay):
+ tabs = (ord(label) - ord('A')) * '\t'
+ n = 3
+ while n > 0:
+ await asyncio.sleep(delay) # <----
+ dt = time.perf_counter() - t0
+ print('━' * 50)
+ print(f'{dt:7.4f}s \t{tabs}{label} = {n}')
+ n -= 1
+
+loop = asyncio.get_event_loop()
+tasks = [
+ loop.create_task(countdown('A', .7)),
+ loop.create_task(countdown('B', 2)),
+ loop.create_task(countdown('C', .3)),
+ loop.create_task(countdown('D', 1)),
+]
+t0 = time.perf_counter()
+loop.run_until_complete(asyncio.wait(tasks))
+loop.close()
+print('━' * 50)
diff --git a/18-asyncio-py3.7/spinner_asyncio.py b/18-asyncio-py3.7/spinner_asyncio.py
old mode 100644
new mode 100755
index b9fc9d5..adbd611
--- a/18-asyncio-py3.7/spinner_asyncio.py
+++ b/18-asyncio-py3.7/spinner_asyncio.py
@@ -1,10 +1,12 @@
-# spinner_await.py
+#!/usr/bin/env python3
+
+# spinner_asyncio.py
# credits: Example by Luciano Ramalho inspired by
# Michele Simionato's multiprocessing example in the python-list:
# https://mail.python.org/pipermail/python-list/2009-February/538048.html
-# BEGIN SPINNER_AWAIT
+# BEGIN SPINNER_ASYNCIO
import asyncio
import itertools
import sys
@@ -18,31 +20,31 @@ async def spin(msg): # <1>
flush()
write('\x08' * len(status))
try:
- await asyncio.sleep(.1) # <3>
- except asyncio.CancelledError: # <4>
+ await asyncio.sleep(.1) # <2>
+ except asyncio.CancelledError: # <3>
break
write(' ' * len(status) + '\x08' * len(status))
-async def slow_function(): # <5>
+async def slow_function(): # <4>
# pretend waiting a long time for I/O
- await asyncio.sleep(3) # <6>
+ await asyncio.sleep(3) # <5>
return 42
-async def supervisor(): # <7>
- spinner = asyncio.create_task(spin('thinking!')) # <8>
- print('spinner object:', spinner) # <9>
- result = await slow_function() # <10>
- spinner.cancel() # <11>
+async def supervisor(): # <6>
+ spinner = asyncio.create_task(spin('thinking!')) # <7>
+ print('spinner object:', spinner) # <8>
+ result = await slow_function() # <9>
+ spinner.cancel() # <10>
return result
def main():
- result = asyncio.run(supervisor()) # <12>
+ result = asyncio.run(supervisor()) # <11>
print('Answer:', result)
if __name__ == '__main__':
main()
-# END SPINNER_AWAIT
+# END SPINNER_ASYNCIO
diff --git a/18-asyncio-py3.7/spinner_curio.py b/18-asyncio-py3.7/spinner_curio.py
deleted file mode 100755
index 0af06d8..0000000
--- a/18-asyncio-py3.7/spinner_curio.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python3
-
-# spinner_curio.py
-
-# credits:
-# Example from the book Fluent Python by Luciano Ramalho inspired by
-# Michele Simionato's multiprocessing example in the python-list:
-# https://mail.python.org/pipermail/python-list/2009-February/538048.html
-# ported to use David Beazley's `curio` library:
-# https://github.com/dabeaz/curio
-
-import curio
-import itertools
-import sys
-
-
-async def spin(msg): # <1>
- write, flush = sys.stdout.write, sys.stdout.flush
- for char in itertools.cycle('|/-\\'):
- status = char + ' ' + msg
- write(status)
- flush()
- write('\x08' * len(status))
- try:
- await curio.sleep(.1) # <2>
- except curio.CancelledError: # <3>
- break
- write(' ' * len(status) + '\x08' * len(status))
-
-
-async def slow_function(): # <4>
- # pretend waiting a long time for I/O
- await curio.sleep(3) # <5>
- return 42
-
-
-async def supervisor(): # <6>
- spinner = await curio.spawn(spin('thinking!')) # <7>
- print('spinner object:\n ', repr(spinner)) # <8>
- result = await slow_function() # <9>
- await spinner.cancel() # <10>
- return result
-
-
-def main():
- result = curio.run(supervisor) # <11>
- print('Answer:', result)
-
-
-if __name__ == '__main__':
- main()
diff --git a/18-asyncio-py3.7/spinner_thread.py b/18-asyncio-py3.7/spinner_thread.py
old mode 100644
new mode 100755
index a907a25..dffcca6
--- a/18-asyncio-py3.7/spinner_thread.py
+++ b/18-asyncio-py3.7/spinner_thread.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python3
+
# spinner_thread.py
# credits: Adapted from Michele Simionato's
From dd164484a3c440adaf8d8331e556e3bd9757091b Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Wed, 23 Jan 2019 22:19:17 -0200
Subject: [PATCH 14/24] updated more ch. 17 examples to Python 3.7
---
17-futures-py3.7/countries/.gitignore | 1 +
17-futures-py3.7/countries/README.rst | 178 ++++++++++++++++++
17-futures-py3.7/countries/country_codes.txt | 8 +
17-futures-py3.7/countries/flags2_asyncio.py | 103 ++++++++++
17-futures-py3.7/countries/flags2_common.py | 149 +++++++++++++++
.../countries/flags2_sequential.py | 87 +++++++++
.../countries/flags2_threadpool.py | 68 +++++++
.../{asyncio_flags.py => flags_asyncio.py} | 0
...hreadpool_flags.py => flags_threadpool.py} | 0
9 files changed, 594 insertions(+)
create mode 100644 17-futures-py3.7/countries/.gitignore
create mode 100644 17-futures-py3.7/countries/README.rst
create mode 100644 17-futures-py3.7/countries/country_codes.txt
create mode 100644 17-futures-py3.7/countries/flags2_asyncio.py
create mode 100644 17-futures-py3.7/countries/flags2_common.py
create mode 100644 17-futures-py3.7/countries/flags2_sequential.py
create mode 100644 17-futures-py3.7/countries/flags2_threadpool.py
rename 17-futures-py3.7/countries/{asyncio_flags.py => flags_asyncio.py} (100%)
rename 17-futures-py3.7/countries/{threadpool_flags.py => flags_threadpool.py} (100%)
diff --git a/17-futures-py3.7/countries/.gitignore b/17-futures-py3.7/countries/.gitignore
new file mode 100644
index 0000000..8ea4ee7
--- /dev/null
+++ b/17-futures-py3.7/countries/.gitignore
@@ -0,0 +1 @@
+flags/
diff --git a/17-futures-py3.7/countries/README.rst b/17-futures-py3.7/countries/README.rst
new file mode 100644
index 0000000..0f29b01
--- /dev/null
+++ b/17-futures-py3.7/countries/README.rst
@@ -0,0 +1,178 @@
+============================
+Setting up Nginx and Vaurien
+============================
+
+This text explains how to configure Nginx and Vaurien to test HTTP client code while avoiding network traffic and introducing simulated delays and errors. This setup is necessary if you want to experiment with the ``flags2*.py`` image download examples in this directory -- covered in chapters 17 and 18 of Fluent Python.
+
+
+Overview
+========
+
+The flag download examples are designed to compare the performance of different approaches to finding and downloading files from the Web. However, we don't want to hit a public server with multiple requests per second while testing, and we want to be able to simulate high latency and random network errors.
+
+For this setup I chose Nginx as the HTTP server because it is very fast and easy to configure, and Toxiproxy — designed by Shopify to introduce delays and network errors for testing distributed systems.
+
+The archive ``flags.zip``, contains a directory ``flags/`` with 194 subdirectories, each containing a ``.gif`` image and a ``metadata.json`` file. These are public-domain images copied from the `CIA World Fact Book `_.
+
+Once these files are unpacked to the ``flags/`` directory and Nginx is configured, you can experiment with the ``flags2*.py`` examples without hitting the network.
+
+
+Procedure
+=========
+
+1. Unpack test data
+-------------------
+
+The instructions in this section are for GNU/Linux or OSX using the command line. Windows users should have no difficulty doing the same operations with the Windows Exporer GUI.
+
+Unpack the initial data in the ``fancy_flags/`` directory::
+
+ $ unzip flags.zip
+ ... many lines omitted ...
+ creating: flags/zw/
+ inflating: flags/zw/metadata.json
+ inflating: flags/zw/zw.gif
+
+
+Verify that 194 directories are created in ``fancy_flags/flags/``, each with a ``.gif`` and a ``metadata.json`` file::
+
+
+ $ ls flags | wc -w
+ 194
+ $ find flags | grep .gif | wc -l
+ 194
+ $ find flags | grep .json | wc -l
+ 194
+ $ ls flags/ad
+ ad.gif metadata.json
+
+
+2. Install Nginx
+----------------
+
+Download and install Nginx. I used version 1.6.2 -- the latest stable version as I write this.
+
+* Download page: http://nginx.org/en/download.html
+
+* Beginner's guide: http://nginx.org/en/docs/beginners_guide.html
+
+
+3. Configure Nginx
+------------------
+
+Edit the the ``nginx.conf`` file to set the port and document root. You can determine which ``nginx.conf`` is in use by running::
+
+
+ $ nginx -V
+
+
+The output starts with::
+
+ nginx version: nginx/1.6.2
+ built by clang 6.0 (clang-600.0.51) (based on LLVM 3.5svn)
+ TLS SNI support enabled
+ configure arguments:...
+
+
+Among the configure arguments you'll see ``--conf-path=``. That's the file you will edit.
+
+Most of the content in ``nginx.conf`` is within a block labeled ``http`` and enclosed in curly braces. Within that block there can be multiple blocks labeled ``server``. Add another ``server`` block like this one::
+
+
+ server {
+ listen 8001;
+
+ location /flags/ {
+ root /full-path-to.../countries/;
+ }
+ }
+
+
+After editing ``nginx.conf`` the server must be started (if it's not running) or told to reload the configuration file::
+
+
+ $ nginx # to start, if necessary
+ $ nginx -s reload # to reload the configuration
+
+
+To test the configuration, open the URL http://localhost:8001/flags/ad/ad.gif in a browser. You should see the blue, yellow and red flag of Andorra.
+
+If the test fails, please double check the procedure just described and refer to the Nginx documentation.
+
+At this point you may run the ``flags_*2.py`` examples against the Nginx install by providing the ``--server LOCAL`` command line option. For example::
+
+
+ $ python3 flags2_threadpool.py -s LOCAL
+ LOCAL site: http://localhost:8001/flags
+ Searching for 20 flags: from BD to VN
+ 20 concurrent connections will be used.
+ --------------------
+ 20 flags downloaded.
+ Elapsed time: 0.09s
+
+
+Note that Nginx is so fast that you will not see much difference in run time between the sequential and the concurrent versions. For more realistic testing with simulated network lag, we need to set up Toxiproxy.
+
+
+4. Install and run Toxiproxy
+----------------------------
+
+Install...
+
+In one terminal:
+
+ $ toxiproxy-server
+
+In another terminal:
+
+ $ toxiproxy-cli create nginx_flags_delay -l localhost:8002 -u localhost:8001
+ Created new proxy nginx_flags_delay
+ $ toxiproxy-cli toxic add nginx_flags_delay -t latency -a latency=500
+ Added downstream latency toxic 'latency_downstream' on proxy 'nginx_flags_delay'
+
+
+This creates an HTTP proxy on port 8002 which adds a 0.5s delay to every response. You can test it with a browser on port 8002: http://localhost:8002/flags/ad/ad.gif -- the flag of Andorra should appear after ½ second.
+
+TODO: UPDATE NEXT PARAGRAPH
+
+There is also the ``XXX`` script which runs a proxy on port 8003 producing errors in 25% of the responses and a .5 s delay to 50% of the responses. You can also test it with the browser on port 8003, but rememeber that errors are expected.
+
+
+Platform-specific instructions
+==============================
+
+
+Nginx setup on Mac OS X
+------------------------
+
+Homebrew (copy & paste code at the bottom of http://brew.sh/)::
+
+
+ $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+ $ brew doctor
+ $ brew install nginx
+
+
+Download and unpack::
+
+Docroot is: /usr/local/var/www
+/usr/local/etc/nginx/nginx.conf
+
+
+::
+
+ To have launchd start nginx at login:
+ ln -sfv /usr/local/opt/nginx/*.plist ~/Library/LaunchAgents
+ Then to load nginx now:
+ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
+ Or, if you don't want/need launchctl, you can just run:
+ nginx
+
+
+
+Nginx setup on Lubuntu 14.04.1 LTS
+----------------------------------
+
+Docroot is: /usr/share/nginx/html
+
+
diff --git a/17-futures-py3.7/countries/country_codes.txt b/17-futures-py3.7/countries/country_codes.txt
new file mode 100644
index 0000000..72c37f0
--- /dev/null
+++ b/17-futures-py3.7/countries/country_codes.txt
@@ -0,0 +1,8 @@
+AD AE AF AG AL AM AO AR AT AU AZ BA BB BD BE BF BG BH BI BJ BN BO BR BS BT
+BW BY BZ CA CD CF CG CH CI CL CM CN CO CR CU CV CY CZ DE DJ DK DM DZ EC EE
+EG ER ES ET FI FJ FM FR GA GB GD GE GH GM GN GQ GR GT GW GY HN HR HT HU ID
+IE IL IN IQ IR IS IT JM JO JP KE KG KH KI KM KN KP KR KW KZ LA LB LC LI LK
+LR LS LT LU LV LY MA MC MD ME MG MH MK ML MM MN MR MT MU MV MW MX MY MZ NA
+NE NG NI NL NO NP NR NZ OM PA PE PG PH PK PL PT PW PY QA RO RS RU RW SA SB
+SC SD SE SG SI SK SL SM SN SO SR SS ST SV SY SZ TD TG TH TJ TL TM TN TO TR
+TT TV TW TZ UA UG US UY UZ VA VC VE VN VU WS YE ZA ZM ZW
diff --git a/17-futures-py3.7/countries/flags2_asyncio.py b/17-futures-py3.7/countries/flags2_asyncio.py
new file mode 100644
index 0000000..2635155
--- /dev/null
+++ b/17-futures-py3.7/countries/flags2_asyncio.py
@@ -0,0 +1,103 @@
+"""Download flags of countries (with error handling).
+
+asyncio async/await version
+
+"""
+# BEGIN FLAGS2_ASYNCIO_TOP
+import asyncio
+import collections
+
+import aiohttp
+from aiohttp import web
+import tqdm
+
+from flags2_common import main, HTTPStatus, Result, save_flag
+
+# default set low to avoid errors from remote site, such as
+# 503 - Service Temporarily Unavailable
+DEFAULT_CONCUR_REQ = 5
+MAX_CONCUR_REQ = 1000
+
+
+class FetchError(Exception): # <1>
+ def __init__(self, country_code):
+ self.country_code = country_code
+
+
+async def get_flag(session, base_url, cc): # <2>
+ url = '{}/{cc}/{cc}.gif'.format(base_url, cc=cc.lower())
+ async with session.get(url) as resp:
+ if resp.status == 200:
+ return await resp.read()
+ elif resp.status == 404:
+ raise web.HTTPNotFound()
+ else:
+ raise aiohttp.HttpProcessingError(
+ code=resp.status, message=resp.reason,
+ headers=resp.headers)
+
+
+async def download_one(session, cc, base_url, semaphore, verbose): # <3>
+ try:
+ async with semaphore: # <4>
+ image = await get_flag(session, base_url, cc) # <5>
+ except web.HTTPNotFound: # <6>
+ status = HTTPStatus.not_found
+ msg = 'not found'
+ except Exception as exc:
+ raise FetchError(cc) from exc # <7>
+ else:
+ save_flag(image, cc.lower() + '.gif') # <8>
+ status = HTTPStatus.ok
+ msg = 'OK'
+
+ if verbose and msg:
+ print(cc, msg)
+
+ return Result(status, cc)
+# END FLAGS2_ASYNCIO_TOP
+
+# BEGIN FLAGS2_ASYNCIO_DOWNLOAD_MANY
+async def downloader_coro(cc_list, base_url, verbose, concur_req): # <1>
+ counter = collections.Counter()
+ semaphore = asyncio.Semaphore(concur_req) # <2>
+ async with aiohttp.ClientSession() as session: # <8>
+ to_do = [download_one(session, cc, base_url, semaphore, verbose)
+ for cc in sorted(cc_list)] # <3>
+
+ to_do_iter = asyncio.as_completed(to_do) # <4>
+ if not verbose:
+ to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5>
+ for future in to_do_iter: # <6>
+ try:
+ res = await future # <7>
+ except FetchError as exc: # <8>
+ country_code = exc.country_code # <9>
+ try:
+ error_msg = exc.__cause__.args[0] # <10>
+ except IndexError:
+ error_msg = exc.__cause__.__class__.__name__ # <11>
+ if verbose and error_msg:
+ msg = '*** Error for {}: {}'
+ print(msg.format(country_code, error_msg))
+ status = HTTPStatus.error
+ else:
+ status = res.status
+
+ counter[status] += 1 # <12>
+
+ return counter # <13>
+
+
+def download_many(cc_list, base_url, verbose, concur_req):
+ loop = asyncio.get_event_loop()
+ coro = downloader_coro(cc_list, base_url, verbose, concur_req)
+ counts = loop.run_until_complete(coro) # <14>
+ loop.close() # <15>
+
+ return counts
+
+
+if __name__ == '__main__':
+ main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ)
+# END FLAGS2_ASYNCIO_DOWNLOAD_MANY
diff --git a/17-futures-py3.7/countries/flags2_common.py b/17-futures-py3.7/countries/flags2_common.py
new file mode 100644
index 0000000..bfa40fb
--- /dev/null
+++ b/17-futures-py3.7/countries/flags2_common.py
@@ -0,0 +1,149 @@
+"""Utilities for second set of flag examples.
+"""
+
+import os
+import time
+import sys
+import string
+import argparse
+from collections import namedtuple
+from enum import Enum
+
+
+Result = namedtuple('Result', 'status data')
+
+HTTPStatus = Enum('Status', 'ok not_found error')
+
+POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
+ 'MX PH VN ET EG DE IR TR CD FR').split()
+
+DEFAULT_CONCUR_REQ = 1
+MAX_CONCUR_REQ = 1
+
+SERVERS = {
+ 'REMOTE': 'http://flupy.org/data/flags',
+ 'LOCAL': 'http://localhost:8001/flags',
+ 'DELAY': 'http://localhost:8002/flags',
+ 'ERROR': 'http://localhost:8003/flags',
+}
+DEFAULT_SERVER = 'LOCAL'
+
+DEST_DIR = 'downloads/'
+COUNTRY_CODES_FILE = 'country_codes.txt'
+
+
+def save_flag(img, filename):
+ path = os.path.join(DEST_DIR, filename)
+ with open(path, 'wb') as fp:
+ fp.write(img)
+
+
+def initial_report(cc_list, actual_req, server_label):
+ if len(cc_list) <= 10:
+ cc_msg = ', '.join(cc_list)
+ else:
+ cc_msg = 'from {} to {}'.format(cc_list[0], cc_list[-1])
+ print('{} site: {}'.format(server_label, SERVERS[server_label]))
+ msg = 'Searching for {} flag{}: {}'
+ plural = 's' if len(cc_list) != 1 else ''
+ print(msg.format(len(cc_list), plural, cc_msg))
+ plural = 's' if actual_req != 1 else ''
+ msg = '{} concurrent connection{} will be used.'
+ print(msg.format(actual_req, plural))
+
+
+def final_report(cc_list, counter, start_time):
+ elapsed = time.time() - start_time
+ print('-' * 20)
+ msg = '{} flag{} downloaded.'
+ plural = 's' if counter[HTTPStatus.ok] != 1 else ''
+ print(msg.format(counter[HTTPStatus.ok], plural))
+ if counter[HTTPStatus.not_found]:
+ print(counter[HTTPStatus.not_found], 'not found.')
+ if counter[HTTPStatus.error]:
+ plural = 's' if counter[HTTPStatus.error] != 1 else ''
+ print('{} error{}.'.format(counter[HTTPStatus.error], plural))
+ print('Elapsed time: {:.2f}s'.format(elapsed))
+
+
+def expand_cc_args(every_cc, all_cc, cc_args, limit):
+ codes = set()
+ A_Z = string.ascii_uppercase
+ if every_cc:
+ codes.update(a+b for a in A_Z for b in A_Z)
+ elif all_cc:
+ with open(COUNTRY_CODES_FILE) as fp:
+ text = fp.read()
+ codes.update(text.split())
+ else:
+ for cc in (c.upper() for c in cc_args):
+ if len(cc) == 1 and cc in A_Z:
+ codes.update(cc+c for c in A_Z)
+ elif len(cc) == 2 and all(c in A_Z for c in cc):
+ codes.add(cc)
+ else:
+ msg = 'each CC argument must be A to Z or AA to ZZ.'
+ raise ValueError('*** Usage error: '+msg)
+ return sorted(codes)[:limit]
+
+
+def process_args(default_concur_req):
+ server_options = ', '.join(sorted(SERVERS))
+ parser = argparse.ArgumentParser(
+ description='Download flags for country codes. '
+ 'Default: top 20 countries by population.')
+ parser.add_argument('cc', metavar='CC', nargs='*',
+ help='country code or 1st letter (eg. B for BA...BZ)')
+ parser.add_argument('-a', '--all', action='store_true',
+ help='get all available flags (AD to ZW)')
+ parser.add_argument('-e', '--every', action='store_true',
+ help='get flags for every possible code (AA...ZZ)')
+ parser.add_argument('-l', '--limit', metavar='N', type=int,
+ help='limit to N first codes', default=sys.maxsize)
+ parser.add_argument('-m', '--max_req', metavar='CONCURRENT', type=int,
+ default=default_concur_req,
+ help='maximum concurrent requests (default={})'
+ .format(default_concur_req))
+ parser.add_argument('-s', '--server', metavar='LABEL',
+ default=DEFAULT_SERVER,
+ help='Server to hit; one of {} (default={})'
+ .format(server_options, DEFAULT_SERVER))
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='output detailed progress info')
+ args = parser.parse_args()
+ if args.max_req < 1:
+ print('*** Usage error: --max_req CONCURRENT must be >= 1')
+ parser.print_usage()
+ sys.exit(1)
+ if args.limit < 1:
+ print('*** Usage error: --limit N must be >= 1')
+ parser.print_usage()
+ sys.exit(1)
+ args.server = args.server.upper()
+ if args.server not in SERVERS:
+ print('*** Usage error: --server LABEL must be one of',
+ server_options)
+ parser.print_usage()
+ sys.exit(1)
+ try:
+ cc_list = expand_cc_args(args.every, args.all, args.cc, args.limit)
+ except ValueError as exc:
+ print(exc.args[0])
+ parser.print_usage()
+ sys.exit(1)
+
+ if not cc_list:
+ cc_list = sorted(POP20_CC)
+ return args, cc_list
+
+
+def main(download_many, default_concur_req, max_concur_req):
+ args, cc_list = process_args(default_concur_req)
+ actual_req = min(args.max_req, max_concur_req, len(cc_list))
+ initial_report(cc_list, actual_req, args.server)
+ base_url = SERVERS[args.server]
+ t0 = time.time()
+ counter = download_many(cc_list, base_url, args.verbose, actual_req)
+ assert sum(counter.values()) == len(cc_list), \
+ 'some downloads are unaccounted for'
+ final_report(cc_list, counter, t0)
diff --git a/17-futures-py3.7/countries/flags2_sequential.py b/17-futures-py3.7/countries/flags2_sequential.py
new file mode 100644
index 0000000..65a7e43
--- /dev/null
+++ b/17-futures-py3.7/countries/flags2_sequential.py
@@ -0,0 +1,87 @@
+"""Download flags of countries (with error handling).
+
+Sequential version
+
+Sample run::
+
+ $ python3 flags2_sequential.py -s DELAY b
+ DELAY site: http://localhost:8002/flags
+ Searching for 26 flags: from BA to BZ
+ 1 concurrent connection will be used.
+ --------------------
+ 17 flags downloaded.
+ 9 not found.
+ Elapsed time: 13.36s
+
+"""
+
+import collections
+
+import requests
+import tqdm
+
+from flags2_common import main, save_flag, HTTPStatus, Result
+
+
+DEFAULT_CONCUR_REQ = 1
+MAX_CONCUR_REQ = 1
+
+# BEGIN FLAGS2_BASIC_HTTP_FUNCTIONS
+def get_flag(base_url, cc):
+ url = '{}/{cc}/{cc}.gif'.format(base_url, cc=cc.lower())
+ resp = requests.get(url)
+ if resp.status_code != 200: # <1>
+ resp.raise_for_status()
+ return resp.content
+
+
+def download_one(cc, base_url, verbose=False):
+ try:
+ image = get_flag(base_url, cc)
+ except requests.exceptions.HTTPError as exc: # <2>
+ res = exc.response
+ if res.status_code == 404:
+ status = HTTPStatus.not_found # <3>
+ msg = 'not found'
+ else: # <4>
+ raise
+ else:
+ save_flag(image, cc.lower() + '.gif')
+ status = HTTPStatus.ok
+ msg = 'OK'
+
+ if verbose: # <5>
+ print(cc, msg)
+
+ return Result(status, cc) # <6>
+# END FLAGS2_BASIC_HTTP_FUNCTIONS
+
+# BEGIN FLAGS2_DOWNLOAD_MANY_SEQUENTIAL
+def download_many(cc_list, base_url, verbose, max_req):
+ counter = collections.Counter() # <1>
+ cc_iter = sorted(cc_list) # <2>
+ if not verbose:
+ cc_iter = tqdm.tqdm(cc_iter) # <3>
+ for cc in cc_iter: # <4>
+ try:
+ res = download_one(cc, base_url, verbose) # <5>
+ except requests.exceptions.HTTPError as exc: # <6>
+ error_msg = 'HTTP error {res.status_code} - {res.reason}'
+ error_msg = error_msg.format(res=exc.response)
+ except requests.exceptions.ConnectionError as exc: # <7>
+ error_msg = 'Connection error'
+ else: # <8>
+ error_msg = ''
+ status = res.status
+
+ if error_msg:
+ status = HTTPStatus.error # <9>
+ counter[status] += 1 # <10>
+ if verbose and error_msg: # <11>
+ print('*** Error for {}: {}'.format(cc, error_msg))
+
+ return counter # <12>
+# END FLAGS2_DOWNLOAD_MANY_SEQUENTIAL
+
+if __name__ == '__main__':
+ main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ)
diff --git a/17-futures-py3.7/countries/flags2_threadpool.py b/17-futures-py3.7/countries/flags2_threadpool.py
new file mode 100644
index 0000000..069d4ff
--- /dev/null
+++ b/17-futures-py3.7/countries/flags2_threadpool.py
@@ -0,0 +1,68 @@
+"""Download flags of countries (with error handling).
+
+ThreadPool version
+
+Sample run::
+
+ $ python3 flags2_threadpool.py -s ERROR -e
+ ERROR site: http://localhost:8003/flags
+ Searching for 676 flags: from AA to ZZ
+ 30 concurrent connections will be used.
+ --------------------
+ 150 flags downloaded.
+ 361 not found.
+ 165 errors.
+ Elapsed time: 7.46s
+
+"""
+
+# BEGIN FLAGS2_THREADPOOL
+import collections
+from concurrent import futures
+
+import requests
+import tqdm # <1>
+
+from flags2_common import main, HTTPStatus # <2>
+from flags2_sequential import download_one # <3>
+
+DEFAULT_CONCUR_REQ = 30 # <4>
+MAX_CONCUR_REQ = 1000 # <5>
+
+
+def download_many(cc_list, base_url, verbose, concur_req):
+ counter = collections.Counter()
+ with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <6>
+ to_do_map = {} # <7>
+ for cc in sorted(cc_list): # <8>
+ future = executor.submit(download_one,
+ cc, base_url, verbose) # <9>
+ to_do_map[future] = cc # <10>
+ done_iter = futures.as_completed(to_do_map) # <11>
+ if not verbose:
+ done_iter = tqdm.tqdm(done_iter, total=len(cc_list)) # <12>
+ for future in done_iter: # <13>
+ try:
+ res = future.result() # <14>
+ except requests.exceptions.HTTPError as exc: # <15>
+ error_msg = 'HTTP {res.status_code} - {res.reason}'
+ error_msg = error_msg.format(res=exc.response)
+ except requests.exceptions.ConnectionError as exc:
+ error_msg = 'Connection error'
+ else:
+ error_msg = ''
+ status = res.status
+
+ if error_msg:
+ status = HTTPStatus.error
+ counter[status] += 1
+ if verbose and error_msg:
+ cc = to_do_map[future] # <16>
+ print('*** Error for {}: {}'.format(cc, error_msg))
+
+ return counter
+
+
+if __name__ == '__main__':
+ main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ)
+# END FLAGS2_THREADPOOL
diff --git a/17-futures-py3.7/countries/asyncio_flags.py b/17-futures-py3.7/countries/flags_asyncio.py
similarity index 100%
rename from 17-futures-py3.7/countries/asyncio_flags.py
rename to 17-futures-py3.7/countries/flags_asyncio.py
diff --git a/17-futures-py3.7/countries/threadpool_flags.py b/17-futures-py3.7/countries/flags_threadpool.py
similarity index 100%
rename from 17-futures-py3.7/countries/threadpool_flags.py
rename to 17-futures-py3.7/countries/flags_threadpool.py
From 9fb76efc35b374835a5a61aaf7cdbb5bdba01ef9 Mon Sep 17 00:00:00 2001
From: anancds
Date: Wed, 20 Mar 2019 22:05:34 +0800
Subject: [PATCH 15/24] fixed anomalous backslash in string
---
03-dict-set/index.py | 2 +-
03-dict-set/index0.py | 2 +-
03-dict-set/index_default.py | 2 +-
14-it-generator/sentence.py | 2 +-
14-it-generator/sentence_gen.py | 2 +-
14-it-generator/sentence_gen2.py | 2 +-
14-it-generator/sentence_genexp.py | 2 +-
14-it-generator/sentence_iter.py | 2 +-
14-it-generator/sentence_iter2.py | 2 +-
18-asyncio-py3.7/charfinder/charfinder.py | 4 ++--
18-asyncio/charfinder/charfinder.py | 2 +-
attic/concurrency/charfinder/charfinder.py | 4 ++--
attic/dicts/index_alex.py | 2 +-
attic/sequences/sentence_slice.py | 4 ++--
14 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/03-dict-set/index.py b/03-dict-set/index.py
index 3eac1fb..f8641d2 100644
--- a/03-dict-set/index.py
+++ b/03-dict-set/index.py
@@ -8,7 +8,7 @@
import sys
import re
-WORD_RE = re.compile('\w+')
+WORD_RE = re.compile(r'\w+')
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
diff --git a/03-dict-set/index0.py b/03-dict-set/index0.py
index e1fa28f..b41af0e 100644
--- a/03-dict-set/index0.py
+++ b/03-dict-set/index0.py
@@ -8,7 +8,7 @@
import sys
import re
-WORD_RE = re.compile('\w+')
+WORD_RE = re.compile(r'\w+')
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
diff --git a/03-dict-set/index_default.py b/03-dict-set/index_default.py
index 521b2d5..8d3ae58 100644
--- a/03-dict-set/index_default.py
+++ b/03-dict-set/index_default.py
@@ -9,7 +9,7 @@
import re
import collections
-WORD_RE = re.compile('\w+')
+WORD_RE = re.compile(r'\w+')
index = collections.defaultdict(list) # <1>
with open(sys.argv[1], encoding='utf-8') as fp:
diff --git a/14-it-generator/sentence.py b/14-it-generator/sentence.py
index fb866c4..6a20c15 100644
--- a/14-it-generator/sentence.py
+++ b/14-it-generator/sentence.py
@@ -5,7 +5,7 @@
import re
import reprlib
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
class Sentence:
diff --git a/14-it-generator/sentence_gen.py b/14-it-generator/sentence_gen.py
index a17c48f..32a8225 100644
--- a/14-it-generator/sentence_gen.py
+++ b/14-it-generator/sentence_gen.py
@@ -5,7 +5,7 @@
import re
import reprlib
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
class Sentence:
diff --git a/14-it-generator/sentence_gen2.py b/14-it-generator/sentence_gen2.py
index 8b0f355..b308100 100644
--- a/14-it-generator/sentence_gen2.py
+++ b/14-it-generator/sentence_gen2.py
@@ -5,7 +5,7 @@
import re
import reprlib
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
class Sentence:
diff --git a/14-it-generator/sentence_genexp.py b/14-it-generator/sentence_genexp.py
index 2919c29..52228de 100644
--- a/14-it-generator/sentence_genexp.py
+++ b/14-it-generator/sentence_genexp.py
@@ -6,7 +6,7 @@
import re
import reprlib
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
class Sentence:
diff --git a/14-it-generator/sentence_iter.py b/14-it-generator/sentence_iter.py
index 938d5b4..11b8179 100644
--- a/14-it-generator/sentence_iter.py
+++ b/14-it-generator/sentence_iter.py
@@ -9,7 +9,7 @@
import re
import reprlib
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
class Sentence:
diff --git a/14-it-generator/sentence_iter2.py b/14-it-generator/sentence_iter2.py
index 8597b32..2663f3f 100644
--- a/14-it-generator/sentence_iter2.py
+++ b/14-it-generator/sentence_iter2.py
@@ -8,7 +8,7 @@
import re
import reprlib
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
class Sentence:
diff --git a/18-asyncio-py3.7/charfinder/charfinder.py b/18-asyncio-py3.7/charfinder/charfinder.py
index 7e06792..64e4949 100755
--- a/18-asyncio-py3.7/charfinder/charfinder.py
+++ b/18-asyncio-py3.7/charfinder/charfinder.py
@@ -64,9 +64,9 @@
import functools
from collections import namedtuple
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
RE_UNICODE_NAME = re.compile('^[A-Z0-9 -]+$')
-RE_CODEPOINT = re.compile('U\+([0-9A-F]{4,6})')
+RE_CODEPOINT = re.compile(r'U\+([0-9A-F]{4,6})')
INDEX_NAME = 'charfinder_index.pickle'
MINIMUM_SAVE_LEN = 10000
diff --git a/18-asyncio/charfinder/charfinder.py b/18-asyncio/charfinder/charfinder.py
index 7e06792..4e97a0a 100755
--- a/18-asyncio/charfinder/charfinder.py
+++ b/18-asyncio/charfinder/charfinder.py
@@ -64,7 +64,7 @@
import functools
from collections import namedtuple
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
RE_UNICODE_NAME = re.compile('^[A-Z0-9 -]+$')
RE_CODEPOINT = re.compile('U\+([0-9A-F]{4,6})')
diff --git a/attic/concurrency/charfinder/charfinder.py b/attic/concurrency/charfinder/charfinder.py
index d73f60b..d18db89 100755
--- a/attic/concurrency/charfinder/charfinder.py
+++ b/attic/concurrency/charfinder/charfinder.py
@@ -63,9 +63,9 @@
import itertools
from collections import namedtuple
-RE_WORD = re.compile('\w+')
+RE_WORD = re.compile(r'\w+')
RE_UNICODE_NAME = re.compile('^[A-Z0-9 -]+$')
-RE_CODEPOINT = re.compile('U\+([0-9A-F]{4,6})')
+RE_CODEPOINT = re.compile(r'U\+([0-9A-F]{4,6})')
INDEX_NAME = 'charfinder_index.pickle'
MINIMUM_SAVE_LEN = 10000
diff --git a/attic/dicts/index_alex.py b/attic/dicts/index_alex.py
index 27d7175..73db8c6 100644
--- a/attic/dicts/index_alex.py
+++ b/attic/dicts/index_alex.py
@@ -8,7 +8,7 @@
import sys
import re
-NONWORD_RE = re.compile('\W+')
+NONWORD_RE = re.compile(r'\W+')
idx = {}
with open(sys.argv[1], encoding='utf-8') as fp:
diff --git a/attic/sequences/sentence_slice.py b/attic/sequences/sentence_slice.py
index d275989..918338d 100644
--- a/attic/sequences/sentence_slice.py
+++ b/attic/sequences/sentence_slice.py
@@ -6,9 +6,9 @@
import reprlib
-RE_TOKEN = re.compile('\w+|\s+|[^\w\s]+')
+RE_TOKEN = re.compile(r'\w+|\s+|[^\w\s]+')
RE_WORD = re.compile('\w+')
-RE_PUNCTUATION = re.compile('[^\w\s]+')
+RE_PUNCTUATION = re.compile(r'[^\w\s]+')
class SentenceSlice:
From a58ecf6bdea847515a8b4871af56e3647849fe8f Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 16 May 2019 18:54:52 -0300
Subject: [PATCH 16/24] simplified spinner_* examples
---
18-asyncio-py3.7/spinner_asyncio.py | 8 ++------
18-asyncio-py3.7/spinner_thread.py | 11 +++--------
2 files changed, 5 insertions(+), 14 deletions(-)
diff --git a/18-asyncio-py3.7/spinner_asyncio.py b/18-asyncio-py3.7/spinner_asyncio.py
index adbd611..369a8f0 100755
--- a/18-asyncio-py3.7/spinner_asyncio.py
+++ b/18-asyncio-py3.7/spinner_asyncio.py
@@ -9,21 +9,17 @@
# BEGIN SPINNER_ASYNCIO
import asyncio
import itertools
-import sys
async def spin(msg): # <1>
- write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
- write(status)
- flush()
- write('\x08' * len(status))
+ print(status, flush=True, end='\r')
try:
await asyncio.sleep(.1) # <2>
except asyncio.CancelledError: # <3>
break
- write(' ' * len(status) + '\x08' * len(status))
+ print(' ' * len(status), end='\r')
async def slow_function(): # <4>
diff --git a/18-asyncio-py3.7/spinner_thread.py b/18-asyncio-py3.7/spinner_thread.py
index dffcca6..bffc921 100755
--- a/18-asyncio-py3.7/spinner_thread.py
+++ b/18-asyncio-py3.7/spinner_thread.py
@@ -10,20 +10,15 @@
import threading
import itertools
import time
-import sys
-def spin(msg, done): # <2>
- write, flush = sys.stdout.write, sys.stdout.flush
+def spin(msg, done): # <1>
for char in itertools.cycle('|/-\\'): # <3>
status = char + ' ' + msg
- write(status)
- flush()
- write('\x08' * len(status)) # <4>
+ print(status, flush=True, end='\r')
if done.wait(.1): # <5>
break
- write(' ' * len(status) + '\x08' * len(status)) # <6>
-
+ print(' ' * len(status), end='\r')
def slow_function(): # <7>
# pretend waiting a long time for I/O
From 7cd136bb680c410c4b9b3601168c19028b0faa92 Mon Sep 17 00:00:00 2001
From: Hunter Duan <308960474@qq.com>
Date: Fri, 15 Nov 2019 16:49:18 +0800
Subject: [PATCH 17/24] fixed the param 'index' is redundant
---
14-it-generator/sentence.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/14-it-generator/sentence.py b/14-it-generator/sentence.py
index 6a20c15..447a192 100644
--- a/14-it-generator/sentence.py
+++ b/14-it-generator/sentence.py
@@ -17,7 +17,7 @@ def __init__(self, text):
def __getitem__(self, index):
return self.words[index] # <2>
- def __len__(self, index): # <3>
+ def __len__(self): # <3>
return len(self.words)
def __repr__(self):
From 93a9ce6407cd8042a8bce82eed5148c9c4eba4e5 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Mon, 2 Mar 2020 15:14:05 -0300
Subject: [PATCH 18/24] updated tcp_charfinder.py to Python 3.8 asyncio API
---
18-asyncio-py3.7/charfinder/tcp_charfinder.py | 23 ++++++-------------
1 file changed, 7 insertions(+), 16 deletions(-)
diff --git a/18-asyncio-py3.7/charfinder/tcp_charfinder.py b/18-asyncio-py3.7/charfinder/tcp_charfinder.py
index 86e27c9..4980b92 100755
--- a/18-asyncio-py3.7/charfinder/tcp_charfinder.py
+++ b/18-asyncio-py3.7/charfinder/tcp_charfinder.py
@@ -38,26 +38,17 @@ async def handle_queries(reader, writer): # <3>
# END TCP_CHARFINDER_TOP
# BEGIN TCP_CHARFINDER_MAIN
-def main(address='127.0.0.1', port=2323): # <1>
+async def main(address='127.0.0.1', port=2323): # <1>
port = int(port)
- loop = asyncio.get_event_loop()
- server_coro = asyncio.start_server(handle_queries, address, port,
- loop=loop) # <2>
- server = loop.run_until_complete(server_coro) # <3>
+ server = await asyncio.start_server(handle_queries, address, port) # <2>
- host = server.sockets[0].getsockname() # <4>
- print('Serving on {}. Hit CTRL-C to stop.'.format(host)) # <5>
- try:
- loop.run_forever() # <6>
- except KeyboardInterrupt: # CTRL+C pressed
- pass
+ host = server.sockets[0].getsockname() # <3>
+ print('Serving on {}. Hit CTRL-C to stop.'.format(host)) # <4>
- print('Server shutting down.')
- server.close() # <7>
- loop.run_until_complete(server.wait_closed()) # <8>
- loop.close() # <9>
+ async with server:
+ await server.serve_forever()
if __name__ == '__main__':
- main(*sys.argv[1:]) # <10>
+ asyncio.run(main(*sys.argv[1:])) # <5>
# END TCP_CHARFINDER_MAIN
From ca6c957cfff5bc3682ee554617e108c7426cf5e9 Mon Sep 17 00:00:00 2001
From: Simon Ilincev
Date: Wed, 26 Aug 2020 11:28:08 +0200
Subject: [PATCH 19/24] correct LookupError
We are no longer inside of the BingoCage class, so we should change the LookupError's wording to reflect that this is an error within the new LotteryBlower class.
---
11-iface-abc/lotto.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/11-iface-abc/lotto.py b/11-iface-abc/lotto.py
index 2295b71..da8c2de 100644
--- a/11-iface-abc/lotto.py
+++ b/11-iface-abc/lotto.py
@@ -17,7 +17,7 @@ def pick(self):
try:
position = random.randrange(len(self._balls)) # <2>
except ValueError:
- raise LookupError('pick from empty BingoCage')
+ raise LookupError('pick from empty LotteryBlower')
return self._balls.pop(position) # <3>
def loaded(self): # <4>
From d920e38720d1002761ae9aa856438bbed4d2d30d Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Sun, 20 Dec 2020 14:12:06 -0300
Subject: [PATCH 20/24] Update README.rst
---
README.rst | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/README.rst b/README.rst
index 99a0ede..fac11b4 100644
--- a/README.rst
+++ b/README.rst
@@ -1,9 +1,7 @@
Fluent Python: example code
===========================
-Example code for the book `Fluent Python`_ by Luciano Ramalho (O'Reilly, 2014).
-
- **BEWARE**: This is a work in progress, like the book itself.
+Example code for the book `Fluent Python, First Edition` by Luciano Ramalho (O'Reilly, 2015).
* Code here may change and disappear without warning.
From 58dbea0b927f5467d7f442541df32d65fa10d55d Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Sun, 20 Dec 2020 14:12:30 -0300
Subject: [PATCH 21/24] Update README.rst
---
README.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.rst b/README.rst
index fac11b4..af75b88 100644
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,5 @@
-Fluent Python: example code
-===========================
+Fluent Python, First Edition: example code
+==========================================
Example code for the book `Fluent Python, First Edition` by Luciano Ramalho (O'Reilly, 2015).
From 169d3c68c4012b7ebe36b79d73e9134a1a3f32de Mon Sep 17 00:00:00 2001
From: Tim Gates
Date: Thu, 24 Dec 2020 16:20:29 +1100
Subject: [PATCH 22/24] docs: fix simple typo, shorcut -> shortcut
There is a small typo in 18-asyncio/charfinder/charfinder.py, attic/concurrency/charfinder/charfinder.py.
Should read `shortcut` rather than `shorcut`.
---
18-asyncio/charfinder/charfinder.py | 2 +-
attic/concurrency/charfinder/charfinder.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/18-asyncio/charfinder/charfinder.py b/18-asyncio/charfinder/charfinder.py
index 4e97a0a..c061f90 100755
--- a/18-asyncio/charfinder/charfinder.py
+++ b/18-asyncio/charfinder/charfinder.py
@@ -163,7 +163,7 @@ def find_chars(self, query, start=0, stop=None):
result_sets = []
for word in tokenize(query):
chars = self.index.get(word)
- if chars is None: # shorcut: no such word
+ if chars is None: # shortcut: no such word
result_sets = []
break
result_sets.append(chars)
diff --git a/attic/concurrency/charfinder/charfinder.py b/attic/concurrency/charfinder/charfinder.py
index d18db89..72e21a4 100755
--- a/attic/concurrency/charfinder/charfinder.py
+++ b/attic/concurrency/charfinder/charfinder.py
@@ -165,7 +165,7 @@ def find_chars(self, query, start=0, stop=None):
for word in tokenize(query):
if word in self.index:
result_sets.append(self.index[word])
- else: # shorcut: no such word
+ else: # shortcut: no such word
result_sets = []
break
if result_sets:
From 7a2a652eab1febebf59ac9bca62c97081a997ee4 Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Sat, 19 Jun 2021 13:44:21 -0300
Subject: [PATCH 23/24] Update README.rst
Fix suggested by @diraol
---
18-asyncio-py3.7/README.rst | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/18-asyncio-py3.7/README.rst b/18-asyncio-py3.7/README.rst
index 0f4f1b8..aad52a3 100644
--- a/18-asyncio-py3.7/README.rst
+++ b/18-asyncio-py3.7/README.rst
@@ -4,8 +4,8 @@ From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
http://shop.oreilly.com/product/0636920032519.do
##################################################################
-NOTE: this "18b" directory contains the examples of chapter 18
-rewritten using the new async/await syntax available in Python 3.5
-ONLY, instead of the "yield-from" syntax which works since Python
-3.3 (and will still work with Python 3.5).
+NOTE: this directory contains the examples of chapter 18
+rewritten using the new async/await syntax available from Python
+3.5+, instead of the "yield-from" syntax of Python 3.3 and 3.4.
+The code was tested with Python 3.7
##################################################################
From d5133ad6e4a48eac0980d2418ed39d7ff693edbe Mon Sep 17 00:00:00 2001
From: Luciano Ramalho
Date: Thu, 2 Dec 2021 11:39:34 -0300
Subject: [PATCH 24/24] Update README.rst
---
README.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.rst b/README.rst
index af75b88..39b95c4 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,8 @@
Fluent Python, First Edition: example code
==========================================
+**This repository is archived and will not be updated. Please visit https://github.com/fluentpython/example-code-2e**
+
Example code for the book `Fluent Python, First Edition` by Luciano Ramalho (O'Reilly, 2015).
* Code here may change and disappear without warning.