diff --git a/.gitignore b/.gitignore
index e7f3b09..956f0dc 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# LR
-.venv36
+.venv3?
*.gif
+.DS_Store
+_build/
+.vscode/
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/Concurrency-mindmap.png b/Concurrency-mindmap.png
new file mode 100644
index 0000000..32bf578
Binary files /dev/null and b/Concurrency-mindmap.png differ
diff --git a/README.md b/README.md
index 88605d0..ce53e5f 100755
--- a/README.md
+++ b/README.md
@@ -1,13 +1,15 @@
-# concurrency
-Example code for the workshop Modern Concurrency in Python
+# Python Concurrency 2017
-## Ideas
+Example code for the workshop **Modern Concurrency in Python**, first presented at PyBay 2017 in San Francisco.
-* Review speakerdeck talks
+## Cloning
-* Update vaurien tutorial
+For faster cloning, get only the latest version of this repo:
-* Load UnicodeData into PostgreSQL to demo [aiopg](http://aiopg.readthedocs.io/en/stable/)
+ $ git clone --depth=1 https://github.com/fluentpython/concurrency.git
+
+
+## References
* [PEP-492: Coroutines with async and await syntax](https://docs.python.org/3/whatsnew/3.5.html#whatsnew-pep-492)
@@ -15,33 +17,28 @@ Example code for the workshop Modern Concurrency in Python
* [PEP 530: Asynchronous Comprehensions](https://docs.python.org/3/whatsnew/3.6.html#pep-530-asynchronous-comprehensions)
-### Benchmarking
+* [Asynchronous Python for the Complete Beginner](https://speakerdeck.com/pycon2017/miguel-grinberg-asynchronous-python-for-the-complete-beginner) ([example code](
+https://gist.github.com/miguelgrinberg/f15bc03471f610cfebeba62438435508) by Miguel Grinberg
-#### ab: Apache Bench
+* [async/await and asyncio in Python 3.6 and beyond](https://speakerdeck.com/1st1/await-and-asyncio-in-python-3-dot-6-and-beyond) ([video](https://www.youtube.com/watch?v=2ZFFv-wZ8_g)) by Yuri Selivanov
-https://www.petefreitag.com/item/689.cfm
+* [Unyielding](https://glyph.twistedmatrix.com/2014/02/unyielding.html): a defense of asynchronous programming by Glyph Lefkowitz, creator of Twisted
-https://serverfault.com/questions/274252/apache-ab-please-explain-the-output
+* [Asynchronous Python and Databases](http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/) by Mike Bayer ([response](https://emptysqua.re/blog/response-to-asynchronous-python-and-databases/) by A. Jesse Jiryu Davis)
-http://infoheap.com/ab-apache-bench-load-testing/ (graphic output)
+* [ConcurrentPython](https://github.com/BruceEckel/ConcurrentPython): notes for an upcoming open book by Bruce Eckel
-### Configuration notes
+## Libraries and frameworks
-#### nginx
+* asyncio
-On Luciano's, Mac, these are the notes output by `brew install nginx`:
+* aiohttp
-```
-Docroot is: /usr/local/var/www
+* aio-libs organization on github
-The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
-nginx can run without sudo.
+* [Sanic](https://github.com/channelcat/sanic)
-nginx will load all files in /usr/local/etc/nginx/servers/.
+* Curio
-To have launchd start nginx now and restart at login:
- brew services start nginx
-Or, if you don't want/need a background service you can just run:
- nginx
-```
+* Trio
diff --git a/attic/NOTES.md b/attic/NOTES.md
new file mode 100755
index 0000000..2d3715a
--- /dev/null
+++ b/attic/NOTES.md
@@ -0,0 +1,58 @@
+# concurrency
+Example code for the workshop Modern Concurrency in Python
+
+## Ideas
+
+* Review speakerdeck talks
+
+* Update vaurien tutorial
+
+* Load UnicodeData into PostgreSQL to demo [aiopg](http://aiopg.readthedocs.io/en/stable/)
+
+* [PEP-492: Coroutines with async and await syntax](https://docs.python.org/3/whatsnew/3.5.html#whatsnew-pep-492)
+
+* [PEP-525: Asynchronous Generators](https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep525)
+
+* [PEP 530: Asynchronous Comprehensions](https://docs.python.org/3/whatsnew/3.6.html#pep-530-asynchronous-comprehensions)
+
+### Benchmarking
+
+#### ab: Apache Bench
+
+https://www.petefreitag.com/item/689.cfm
+
+https://serverfault.com/questions/274252/apache-ab-please-explain-the-output
+
+http://infoheap.com/ab-apache-bench-load-testing/ (graphic output)
+
+concurrency * timetaken * 1000 / done
+ timetaken * 1000 / done
+
+
+### Configuration notes
+
+#### nginx
+
+On Luciano's, Mac, these are the notes output by `brew install nginx`:
+
+```
+Docroot is: /usr/local/var/www
+
+The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
+nginx can run without sudo.
+
+nginx will load all files in /usr/local/etc/nginx/servers/.
+
+To have launchd start nginx now and restart at login:
+ brew services start nginx
+Or, if you don't want/need a background service you can just run:
+ nginx
+```
+
+## References
+
+https://speakerdeck.com/pycon2017/miguel-grinberg-asynchronous-python-for-the-complete-beginner
+
+https://speakerdeck.com/1st1/await-and-asyncio-in-python-3-dot-6-and-beyond
+
+https://glyph.twistedmatrix.com/2014/02/unyielding.html
diff --git a/countries/.flake8 b/countries/.flake8
deleted file mode 100644
index c247e7a..0000000
--- a/countries/.flake8
+++ /dev/null
@@ -1,2 +0,0 @@
-[flake8]
-ignore = E126, E128 # continuation line indentation issues
diff --git a/countries/ab/flags-cloud.md b/countries/ab/flags-cloud.md
deleted file mode 100644
index e19e083..0000000
--- a/countries/ab/flags-cloud.md
+++ /dev/null
@@ -1,341 +0,0 @@
-# AB tests versus `flupy.org` behind Cloudflare
-
-## 100 requests, concurrency 10
-
-```
-$ ab -n 100 -c 10 -e n0100e0010.csv http://flupy.org/data/flags/ar/ar.gif
-This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
-Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
-Licensed to The Apache Software Foundation, http://www.apache.org/
-
-Benchmarking flupy.org (be patient).....done
-
-
-Server Software: cloudflare-nginx
-Server Hostname: flupy.org
-Server Port: 80
-
-Document Path: /data/flags/ar/ar.gif
-Document Length: 6502 bytes
-
-Concurrency Level: 10
-Time taken for tests: 1.267 seconds
-Complete requests: 100
-Failed requests: 0
-Total transferred: 702400 bytes
-HTML transferred: 650200 bytes
-Requests per second: 78.91 [#/sec] (mean)
-Time per request: 126.721 [ms] (mean)
-Time per request: 12.672 [ms] (mean, across all concurrent requests)
-Transfer rate: 541.30 [Kbytes/sec] received
-
-Connection Times (ms)
- min mean[+/-sd] median max
-Connect: 2 62 31.1 77 110
-Processing: 4 63 32.4 80 112
-Waiting: 4 62 32.1 79 110
-Total: 7 126 62.1 146 222
-
-Percentage of the requests served within a certain time (ms)
- 50% 146
- 66% 167
- 75% 168
- 80% 169
- 90% 172
- 95% 181
- 98% 217
- 99% 222
- 100% 222 (longest request)
-```
-
-## 1000 requests, concurrency 100
-
-```
-$ ab -n 1000 -c 0100 -e n1000e0100.csv http://flupy.org/data/flags/ar/ar.gif
-This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
-Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
-Licensed to The Apache Software Foundation, http://www.apache.org/
-
-Benchmarking flupy.org (be patient)
-Completed 100 requests
-Completed 200 requests
-Completed 300 requests
-Completed 400 requests
-Completed 500 requests
-Completed 600 requests
-Completed 700 requests
-Completed 800 requests
-Completed 900 requests
-Completed 1000 requests
-Finished 1000 requests
-
-
-Server Software: cloudflare-nginx
-Server Hostname: flupy.org
-Server Port: 80
-
-Document Path: /data/flags/ar/ar.gif
-Document Length: 6502 bytes
-
-Concurrency Level: 100
-Time taken for tests: 1.245 seconds
-Complete requests: 1000
-Failed requests: 0
-Total transferred: 7024000 bytes
-HTML transferred: 6502000 bytes
-Requests per second: 803.17 [#/sec] (mean)
-Time per request: 124.507 [ms] (mean)
-Time per request: 1.245 [ms] (mean, across all concurrent requests)
-Transfer rate: 5509.22 [Kbytes/sec] received
-
-Connection Times (ms)
- min mean[+/-sd] median max
-Connect: 4 56 111.9 47 1087
-Processing: 7 61 57.3 51 666
-Waiting: 6 55 51.7 49 588
-Total: 13 117 126.9 98 1208
-
-Percentage of the requests served within a certain time (ms)
- 50% 98
- 66% 101
- 75% 104
- 80% 109
- 90% 138
- 95% 176
- 98% 592
- 99% 1106
- 100% 1208 (longest request)
-
- ```
-
- ## 100 requests, concurrency 10, 2nd try
-
- ```
-
-$ ab -n 100 -c 10 -e n0100e0010t2.csv http://flupy.org/data/flags/ar/ar.gif
-This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
-Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
-Licensed to The Apache Software Foundation, http://www.apache.org/
-
-Benchmarking flupy.org (be patient).....done
-
-
-Server Software: cloudflare-nginx
-Server Hostname: flupy.org
-Server Port: 80
-
-Document Path: /data/flags/ar/ar.gif
-Document Length: 6502 bytes
-
-Concurrency Level: 10
-Time taken for tests: 2.038 seconds
-Complete requests: 100
-Failed requests: 0
-Total transferred: 702400 bytes
-HTML transferred: 650200 bytes
-Requests per second: 49.08 [#/sec] (mean)
-Time per request: 203.754 [ms] (mean)
-Time per request: 20.375 [ms] (mean, across all concurrent requests)
-Transfer rate: 336.65 [Kbytes/sec] received
-
-Connection Times (ms)
- min mean[+/-sd] median max
-Connect: 28 89 20.0 89 147
-Processing: 28 107 85.4 92 696
-Waiting: 28 95 39.5 90 388
-Total: 66 195 93.9 181 809
-
-Percentage of the requests served within a certain time (ms)
- 50% 181
- 66% 190
- 75% 193
- 80% 197
- 90% 236
- 95% 299
- 98% 664
- 99% 809
- 100% 809 (longest request)
-
- ```
-
- ## 9999 requests, concurrency 300
-
- ```
-$ ab -n 9999 -c 300 -e n9999e0300.csv http://flupy.org/data/flags/ar/ar.gif
-This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
-Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
-Licensed to The Apache Software Foundation, http://www.apache.org/
-
-Benchmarking flupy.org (be patient)
-socket: Too many open files (24)
-```
-
-## 9999 requests, concurrency 200
-
-```
-$ ab -n 9999 -c 200 -e n9999e0200.csv http://flupy.org/data/flags/ar/ar.gif
-This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
-Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
-Licensed to The Apache Software Foundation, http://www.apache.org/
-
-Benchmarking flupy.org (be patient)
-Completed 999 requests
-Completed 1998 requests
-Completed 2997 requests
-Completed 3996 requests
-Completed 4995 requests
-Completed 5994 requests
-Completed 6993 requests
-Completed 7992 requests
-Completed 8991 requests
-Completed 9990 requests
-Finished 9999 requests
-
-
-Server Software: cloudflare-nginx
-Server Hostname: flupy.org
-Server Port: 80
-
-Document Path: /data/flags/ar/ar.gif
-Document Length: 6502 bytes
-
-Concurrency Level: 200
-Time taken for tests: 27.379 seconds
-Complete requests: 9999
-Failed requests: 1
- (Connect: 0, Receive: 0, Length: 1, Exceptions: 0)
-Total transferred: 70227332 bytes
-HTML transferred: 65007854 bytes
-Requests per second: 365.21 [#/sec] (mean)
-Time per request: 547.632 [ms] (mean)
-Time per request: 2.738 [ms] (mean, across all concurrent requests)
-Transfer rate: 2504.90 [Kbytes/sec] received
-
-Connection Times (ms)
- min mean[+/-sd] median max
-Connect: 0 104 138.8 88 2678
-Processing: 7 144 219.6 91 15096
-Waiting: 6 100 61.2 89 1115
-Total: 18 248 269.7 176 15096
-
-Percentage of the requests served within a certain time (ms)
- 50% 176
- 66% 181
- 75% 185
- 80% 190
- 90% 532
- 95% 606
- 98% 1163
- 99% 1222
- 100% 15096 (longest request)
-
-```
-
-## 100 requests, concurrency 1
-
-```
- $ ab -n 100 -c 1 -e n0100e0001.csv http://flupy.org/data/flags/ar/ar.gif
- This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
- Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Licensed to The Apache Software Foundation, http://www.apache.org/
-
- Benchmarking flupy.org (be patient).....done
-
-
- Server Software: cloudflare-nginx
- Server Hostname: flupy.org
- Server Port: 80
-
- Document Path: /data/flags/ar/ar.gif
- Document Length: 6502 bytes
-
- Concurrency Level: 1
- Time taken for tests: 1.221 seconds
- Complete requests: 100
- Failed requests: 0
- Total transferred: 702400 bytes
- HTML transferred: 650200 bytes
- Requests per second: 81.88 [#/sec] (mean)
- Time per request: 12.213 [ms] (mean)
- Time per request: 12.213 [ms] (mean, across all concurrent requests)
- Transfer rate: 561.65 [Kbytes/sec] received
-
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 3 4 4.4 3 25
- Processing: 4 8 5.5 6 35
- Waiting: 4 7 5.5 5 34
- Total: 7 12 9.5 9 53
-
- Percentage of the requests served within a certain time (ms)
- 50% 9
- 66% 10
- 75% 11
- 80% 12
- 90% 15
- 95% 44
- 98% 50
- 99% 53
- 100% 53 (longest request)
-
-```
-
-## 1000 requests, concurrency 1
-
-```
-
-$ ab -n 1000 -c 0001 -e n1000e0001.csv http://flupy.org/data/flags/ar/ar.gif
- This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
- Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
- Licensed to The Apache Software Foundation, http://www.apache.org/
-
- Benchmarking flupy.org (be patient)
- Completed 100 requests
- Completed 200 requests
- Completed 300 requests
- Completed 400 requests
- Completed 500 requests
- Completed 600 requests
- Completed 700 requests
- Completed 800 requests
- Completed 900 requests
- Completed 1000 requests
- Finished 1000 requests
-
-
- Server Software: cloudflare-nginx
- Server Hostname: flupy.org
- Server Port: 80
-
- Document Path: /data/flags/ar/ar.gif
- Document Length: 6502 bytes
-
- Concurrency Level: 1
- Time taken for tests: 10.673 seconds
- Complete requests: 1000
- Failed requests: 0
- Total transferred: 7024000 bytes
- HTML transferred: 6502000 bytes
- Requests per second: 93.69 [#/sec] (mean)
- Time per request: 10.673 [ms] (mean)
- Time per request: 10.673 [ms] (mean, across all concurrent requests)
- Transfer rate: 642.66 [Kbytes/sec] received
-
- Connection Times (ms)
- min mean[+/-sd] median max
- Connect: 2 4 2.9 3 27
- Processing: 4 7 4.6 5 83
- Waiting: 4 6 4.6 5 82
- Total: 7 11 6.8 9 86
-
- Percentage of the requests served within a certain time (ms)
- 50% 9
- 66% 9
- 75% 9
- 80% 10
- 90% 14
- 95% 25
- 98% 34
- 99% 41
- 100% 86 (longest request)
-```
diff --git a/countries/ab/n0100e0001.csv b/countries/ab/n0100e0001.csv
deleted file mode 100644
index b0da320..0000000
--- a/countries/ab/n0100e0001.csv
+++ /dev/null
@@ -1,102 +0,0 @@
-Percentage served,Time in ms
-0,7.427
-1,7.470
-2,7.665
-3,7.675
-4,7.704
-5,7.737
-6,7.766
-7,7.830
-8,7.835
-9,7.844
-10,7.914
-11,7.975
-12,7.992
-13,7.999
-14,8.003
-15,8.026
-16,8.053
-17,8.119
-18,8.214
-19,8.219
-20,8.232
-21,8.275
-22,8.352
-23,8.355
-24,8.397
-25,8.406
-26,8.442
-27,8.459
-28,8.473
-29,8.473
-30,8.481
-31,8.493
-32,8.503
-33,8.510
-34,8.521
-35,8.571
-36,8.596
-37,8.612
-38,8.636
-39,8.648
-40,8.664
-41,8.740
-42,8.770
-43,8.805
-44,8.844
-45,8.847
-46,8.850
-47,8.865
-48,8.872
-49,8.893
-50,8.944
-51,9.063
-52,9.070
-53,9.090
-54,9.112
-55,9.144
-56,9.156
-57,9.269
-58,9.277
-59,9.380
-60,9.502
-61,9.506
-62,9.595
-63,9.711
-64,9.742
-65,9.855
-66,10.147
-67,10.536
-68,10.563
-69,10.566
-70,10.588
-71,10.612
-72,10.715
-73,10.797
-74,10.918
-75,11.280
-76,11.287
-77,11.408
-78,11.542
-79,11.607
-80,11.621
-81,11.857
-82,11.921
-83,11.994
-84,12.266
-85,12.422
-86,12.670
-87,13.772
-88,13.824
-89,13.892
-90,15.206
-91,15.726
-92,24.376
-93,37.684
-94,38.007
-95,43.508
-96,43.569
-97,49.315
-98,50.486
-99,52.993
-100,52.993
diff --git a/countries/ab/n0100e0010.csv b/countries/ab/n0100e0010.csv
deleted file mode 100644
index f18d0dc..0000000
--- a/countries/ab/n0100e0010.csv
+++ /dev/null
@@ -1,102 +0,0 @@
-Percentage served,Time in ms
-0,7.059
-1,7.596
-2,8.016
-3,8.020
-4,8.239
-5,8.564
-6,8.678
-7,8.737
-8,9.261
-9,9.360
-10,9.471
-11,9.965
-12,10.058
-13,10.575
-14,11.057
-15,12.082
-16,12.485
-17,14.160
-18,20.343
-19,22.806
-20,29.335
-21,109.409
-22,115.832
-23,118.417
-24,118.466
-25,120.531
-26,123.001
-27,123.978
-28,124.951
-29,126.178
-30,131.502
-31,133.020
-32,134.765
-33,135.688
-34,135.706
-35,136.182
-36,137.771
-37,138.516
-38,138.893
-39,139.178
-40,139.992
-41,140.202
-42,141.254
-43,142.095
-44,143.480
-45,144.597
-46,145.011
-47,145.102
-48,145.192
-49,145.637
-50,146.383
-51,147.120
-52,148.942
-53,149.105
-54,149.790
-55,150.703
-56,153.495
-57,155.257
-58,161.374
-59,162.249
-60,163.377
-61,163.392
-62,164.145
-63,165.410
-64,165.542
-65,166.282
-66,166.760
-67,166.988
-68,167.299
-69,167.331
-70,167.366
-71,167.470
-72,167.555
-73,167.749
-74,167.793
-75,168.142
-76,168.464
-77,168.584
-78,168.692
-79,168.751
-80,168.881
-81,169.560
-82,169.883
-83,169.902
-84,170.065
-85,170.398
-86,170.619
-87,171.024
-88,171.294
-89,171.502
-90,171.570
-91,172.657
-92,172.872
-93,174.254
-94,175.644
-95,180.590
-96,187.087
-97,204.680
-98,217.420
-99,221.735
-100,221.735
diff --git a/countries/ab/n0100e0010t2.csv b/countries/ab/n0100e0010t2.csv
deleted file mode 100644
index 421a445..0000000
--- a/countries/ab/n0100e0010t2.csv
+++ /dev/null
@@ -1,102 +0,0 @@
-Percentage served,Time in ms
-0,65.614
-1,72.422
-2,77.543
-3,86.466
-4,96.534
-5,104.387
-6,120.018
-7,123.175
-8,127.661
-9,131.614
-10,147.416
-11,154.789
-12,156.151
-13,162.578
-14,165.345
-15,165.714
-16,166.060
-17,168.508
-18,169.409
-19,170.234
-20,170.331
-21,170.541
-22,170.958
-23,171.218
-24,171.414
-25,171.415
-26,171.667
-27,171.828
-28,171.902
-29,172.011
-30,172.224
-31,172.328
-32,172.457
-33,172.713
-34,172.811
-35,174.633
-36,175.678
-37,176.187
-38,176.244
-39,176.506
-40,177.384
-41,177.733
-42,178.414
-43,178.426
-44,178.663
-45,178.956
-46,179.978
-47,180.108
-48,180.210
-49,180.579
-50,180.895
-51,181.483
-52,181.584
-53,181.857
-54,182.408
-55,183.022
-56,183.560
-57,184.588
-58,184.619
-59,185.133
-60,185.266
-61,186.190
-62,187.621
-63,188.389
-64,188.540
-65,189.885
-66,190.071
-67,190.310
-68,190.509
-69,190.748
-70,190.835
-71,191.355
-72,191.482
-73,191.869
-74,192.568
-75,192.871
-76,193.079
-77,193.288
-78,193.315
-79,196.605
-80,196.682
-81,197.297
-82,200.862
-83,214.267
-84,216.634
-85,223.778
-86,228.908
-87,230.225
-88,231.230
-89,235.263
-90,235.654
-91,237.342
-92,238.538
-93,238.723
-94,245.182
-95,298.897
-96,363.656
-97,511.900
-98,663.529
-99,808.914
-100,808.914
diff --git a/countries/ab/n1000e0001.csv b/countries/ab/n1000e0001.csv
deleted file mode 100644
index 90cf587..0000000
--- a/countries/ab/n1000e0001.csv
+++ /dev/null
@@ -1,102 +0,0 @@
-Percentage served,Time in ms
-0,6.722
-1,7.330
-2,7.531
-3,7.572
-4,7.613
-5,7.669
-6,7.696
-7,7.733
-8,7.773
-9,7.800
-10,7.830
-11,7.859
-12,7.886
-13,7.918
-14,7.947
-15,7.959
-16,7.987
-17,8.004
-18,8.022
-19,8.049
-20,8.060
-21,8.076
-22,8.098
-23,8.118
-24,8.138
-25,8.160
-26,8.180
-27,8.193
-28,8.222
-29,8.239
-30,8.258
-31,8.276
-32,8.302
-33,8.323
-34,8.337
-35,8.347
-36,8.371
-37,8.388
-38,8.404
-39,8.423
-40,8.438
-41,8.460
-42,8.501
-43,8.514
-44,8.523
-45,8.545
-46,8.559
-47,8.595
-48,8.606
-49,8.616
-50,8.648
-51,8.688
-52,8.695
-53,8.710
-54,8.718
-55,8.746
-56,8.761
-57,8.777
-58,8.808
-59,8.838
-60,8.852
-61,8.890
-62,8.908
-63,8.940
-64,8.961
-65,8.983
-66,9.016
-67,9.047
-68,9.078
-69,9.109
-70,9.146
-71,9.188
-72,9.237
-73,9.290
-74,9.368
-75,9.416
-76,9.488
-77,9.525
-78,9.583
-79,9.649
-80,9.713
-81,9.831
-82,10.062
-83,10.182
-84,10.395
-85,10.619
-86,10.841
-87,11.343
-88,11.673
-89,12.305
-90,13.551
-91,14.577
-92,18.150
-93,21.034
-94,23.908
-95,25.475
-96,29.493
-97,30.661
-98,33.947
-99,40.894
-100,85.905
diff --git a/countries/ab/n1000e0100.csv b/countries/ab/n1000e0100.csv
deleted file mode 100644
index d037b07..0000000
--- a/countries/ab/n1000e0100.csv
+++ /dev/null
@@ -1,102 +0,0 @@
-Percentage served,Time in ms
-0,12.794
-1,19.647
-2,26.478
-3,29.849
-4,33.869
-5,41.255
-6,45.026
-7,50.891
-8,58.011
-9,66.561
-10,70.069
-11,72.978
-12,77.119
-13,81.467
-14,84.203
-15,87.646
-16,88.583
-17,89.706
-18,90.290
-19,90.700
-20,90.945
-21,91.363
-22,91.652
-23,91.843
-24,92.148
-25,92.450
-26,92.827
-27,92.908
-28,93.113
-29,93.327
-30,93.515
-31,93.737
-32,93.970
-33,94.194
-34,94.381
-35,94.690
-36,94.871
-37,95.158
-38,95.435
-39,95.620
-40,95.872
-41,96.135
-42,96.420
-43,96.648
-44,96.888
-45,97.068
-46,97.288
-47,97.568
-48,97.693
-49,97.958
-50,98.112
-51,98.277
-52,98.391
-53,98.672
-54,98.800
-55,98.964
-56,99.225
-57,99.434
-58,99.570
-59,99.751
-60,99.992
-61,100.184
-62,100.292
-63,100.732
-64,100.903
-65,101.101
-66,101.479
-67,101.735
-68,101.983
-69,102.372
-70,102.521
-71,102.861
-72,103.160
-73,103.576
-74,103.890
-75,104.053
-76,104.485
-77,104.795
-78,105.633
-79,106.406
-80,108.903
-81,110.473
-82,111.838
-83,116.042
-84,119.498
-85,122.380
-86,123.284
-87,126.112
-88,130.824
-89,133.552
-90,138.026
-91,141.577
-92,153.834
-93,161.887
-94,168.457
-95,175.649
-96,182.238
-97,195.409
-98,592.348
-99,1106.487
-100,1207.801
diff --git a/countries/ab/n9999e0200.csv b/countries/ab/n9999e0200.csv
deleted file mode 100644
index cedb4cc..0000000
--- a/countries/ab/n9999e0200.csv
+++ /dev/null
@@ -1,102 +0,0 @@
-Percentage served,Time in ms
-0,17.628
-1,124.198
-2,145.919
-3,155.990
-4,158.761
-5,160.118
-6,161.473
-7,162.402
-8,163.260
-9,163.932
-10,164.506
-11,165.076
-12,165.518
-13,165.950
-14,166.362
-15,166.851
-16,167.217
-17,167.645
-18,167.966
-19,168.354
-20,168.684
-21,169.035
-22,169.355
-23,169.654
-24,169.902
-25,170.265
-26,170.538
-27,170.775
-28,171.012
-29,171.297
-30,171.591
-31,171.899
-32,172.161
-33,172.388
-34,172.670
-35,172.911
-36,173.162
-37,173.376
-38,173.635
-39,173.890
-40,174.141
-41,174.365
-42,174.601
-43,174.839
-44,175.078
-45,175.288
-46,175.515
-47,175.747
-48,175.977
-49,176.197
-50,176.431
-51,176.698
-52,176.919
-53,177.130
-54,177.358
-55,177.590
-56,177.838
-57,178.081
-58,178.294
-59,178.522
-60,178.825
-61,179.110
-62,179.426
-63,179.715
-64,179.978
-65,180.305
-66,180.625
-67,180.966
-68,181.351
-69,181.682
-70,182.087
-71,182.553
-72,182.993
-73,183.488
-74,184.016
-75,184.579
-76,185.254
-77,186.011
-78,186.994
-79,188.082
-80,189.640
-81,193.179
-82,202.305
-83,211.150
-84,219.600
-85,229.808
-86,247.611
-87,320.197
-88,419.946
-89,513.104
-90,532.179
-91,540.923
-92,549.520
-93,564.344
-94,579.393
-95,606.283
-96,667.960
-97,759.356
-98,1162.608
-99,1221.905
-100,15096.487
diff --git a/exercise1/countdown.py b/exercise1/countdown.py
deleted file mode 100755
index 5e5b925..0000000
--- a/exercise1/countdown.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python3
-
-# Inspired by
-# https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/
-
-"""
-The `async/await` keywords enable cooperative multitasking, but that only
-works when functions *cooperate* by using `await` to let the scheduler run
-other tasks concurrently.
-
-As an experiment, replace the line labeled `` with this::
-
- time.sleep(delay)
-
-Save and run the program to see the effect. Discuss with your neighboors and
-with the instructor.
-
-A key lesson of this example is that an `async` function which never does
-`await` is really synchronous.
-"""
-
-
-import asyncio
-import time
-
-
-async def countdown(label, delay):
- tabs = (ord(label) - ord('A')) * '\t'
- n = 3
- while n > 0:
- await asyncio.sleep(delay) # <----
- # time.sleep(delay)
- dt = time.perf_counter() - t0
- print('{:7.4f}s \t{}{} = {}'.format(dt, 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()
diff --git a/lab-countdown/countdown.py b/lab-countdown/countdown.py
new file mode 100755
index 0000000..5f09000
--- /dev/null
+++ b/lab-countdown/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/countries/.gitignore b/lab-flags/.gitignore
similarity index 100%
rename from countries/.gitignore
rename to lab-flags/.gitignore
diff --git a/countries/README.rst b/lab-flags/README.rst
similarity index 100%
rename from countries/README.rst
rename to lab-flags/README.rst
diff --git a/countries/country_codes.txt b/lab-flags/country_codes.txt
similarity index 100%
rename from countries/country_codes.txt
rename to lab-flags/country_codes.txt
diff --git a/lab-flags/downloads/README.txt b/lab-flags/downloads/README.txt
new file mode 100644
index 0000000..4c93e6d
--- /dev/null
+++ b/lab-flags/downloads/README.txt
@@ -0,0 +1,2 @@
+This file exists so that this directory is saved to git, to
+make it easier to run the scripts in the lab-flags directory.
diff --git a/countries/flags.zip b/lab-flags/extra/flags.zip
similarity index 100%
rename from countries/flags.zip
rename to lab-flags/extra/flags.zip
diff --git a/countries/flags2_await.py b/lab-flags/extra/flags2_await.py
similarity index 100%
rename from countries/flags2_await.py
rename to lab-flags/extra/flags2_await.py
diff --git a/countries/flags2_await_executor.py b/lab-flags/extra/flags2_await_executor.py
similarity index 100%
rename from countries/flags2_await_executor.py
rename to lab-flags/extra/flags2_await_executor.py
diff --git a/countries/flags2_common.py b/lab-flags/extra/flags2_common.py
similarity index 100%
rename from countries/flags2_common.py
rename to lab-flags/extra/flags2_common.py
diff --git a/countries/flags2_sequential.py b/lab-flags/extra/flags2_sequential.py
similarity index 100%
rename from countries/flags2_sequential.py
rename to lab-flags/extra/flags2_sequential.py
diff --git a/countries/flags2_threadpool.py b/lab-flags/extra/flags2_threadpool.py
similarity index 100%
rename from countries/flags2_threadpool.py
rename to lab-flags/extra/flags2_threadpool.py
diff --git a/countries/flags3_await.py b/lab-flags/extra/flags3_await.py
similarity index 100%
rename from countries/flags3_await.py
rename to lab-flags/extra/flags3_await.py
diff --git a/countries/flags3_threadpool.py b/lab-flags/extra/flags3_threadpool.py
similarity index 100%
rename from countries/flags3_threadpool.py
rename to lab-flags/extra/flags3_threadpool.py
diff --git a/countries/flags_threadpool_ac.py b/lab-flags/extra/flags_threadpool_ac.py
similarity index 100%
rename from countries/flags_threadpool_ac.py
rename to lab-flags/extra/flags_threadpool_ac.py
diff --git a/countries/vaurien_delay.sh b/lab-flags/extra/vaurien_delay.sh
similarity index 100%
rename from countries/vaurien_delay.sh
rename to lab-flags/extra/vaurien_delay.sh
diff --git a/countries/vaurien_error_delay.sh b/lab-flags/extra/vaurien_error_delay.sh
similarity index 100%
rename from countries/vaurien_error_delay.sh
rename to lab-flags/extra/vaurien_error_delay.sh
diff --git a/countries/flags.py b/lab-flags/flags.py
similarity index 100%
rename from countries/flags.py
rename to lab-flags/flags.py
diff --git a/countries/flags_await.py b/lab-flags/flags_await.py
similarity index 84%
rename from countries/flags_await.py
rename to lab-flags/flags_await.py
index 3832da7..94d8352 100755
--- a/countries/flags_await.py
+++ b/lab-flags/flags_await.py
@@ -13,6 +13,7 @@
"""
import asyncio
+import socket
import aiohttp # <1>
@@ -34,7 +35,9 @@ async def download_one(client, cc): # <6>
async def download_many(loop, cc_list):
- async with aiohttp.ClientSession(loop=loop) as client: # <8>
+ tcpconnector = aiohttp.TCPConnector(family=socket.AF_INET)
+ async with aiohttp.ClientSession(connector=tcpconnector) as client:
+ # async with aiohttp.ClientSession(loop=loop) as client: # <8>
to_do = [download_one(client, cc) for cc in sorted(cc_list)] # <9>
res = await asyncio.gather(*to_do)
return len(res) # <10>
diff --git a/countries/flags_threadpool.py b/lab-flags/flags_threadpool.py
similarity index 100%
rename from countries/flags_threadpool.py
rename to lab-flags/flags_threadpool.py
diff --git a/lab-native-coros/demo1.py b/lab-native-coros/demo1.py
new file mode 100755
index 0000000..afa1389
--- /dev/null
+++ b/lab-native-coros/demo1.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+
+# The simplest native coroutine demo
+# (that I could imagine)
+
+import types
+
+
+@types.coroutine
+def gen():
+ yield 42
+
+
+async def delegating():
+ await gen()
+
+
+# The driving code starts here:
+coro = delegating()
+
+res = coro.send(None)
+print(res)
+
+res = coro.send(None)
+print(res) # Never executed. Why?
diff --git a/lab-native-coros/demo2.py b/lab-native-coros/demo2.py
new file mode 100755
index 0000000..b52a1f9
--- /dev/null
+++ b/lab-native-coros/demo2.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+# A slightly more interesting demo
+# Now the generator-coroutine yields 3 times.
+
+import types
+
+
+@types.coroutine
+def gen123():
+ yield 1
+ yield 2
+ yield 3
+
+
+async def delegating():
+ await gen123()
+
+
+# Driving code:
+coro = delegating()
+res = coro.send(None)
+print(res)
+
+res = coro.send(None)
+print(res)
+
+res = coro.send(None)
+print(res)
+
+coro.send(None) # --> StopIteration
+
+# coro.send(None) # --> RuntimeError
diff --git a/lab-native-coros/demo3.py b/lab-native-coros/demo3.py
new file mode 100755
index 0000000..e93c418
--- /dev/null
+++ b/lab-native-coros/demo3.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+# A generator-coroutine that receives values
+# The driving code can send values other than `None`.
+
+import types
+
+
+@types.coroutine
+def times10(terms):
+ n = yield 'Primed!'
+ for _ in range(terms):
+ n = yield n * 10
+ return n * 10
+
+
+async def delegating(terms):
+ res = await times10(terms)
+ return res
+
+# Driving code must *prime* the coroutine by sending `None` initially:
+
+coro = delegating(3)
+res = coro.send(None)
+print('send(None):', res)
+
+res = coro.send(5)
+print('send(5):', res)
+
+res = coro.send(6)
+print('send(6):', res)
+
+res = coro.send(7)
+print('send(7):', res)
+
+try:
+ coro.send(8)
+except StopIteration as e:
+ res = e.value
+print('send(8):', res)
diff --git a/lab-native-coros/native-coro-demos.ipynb b/lab-native-coros/native-coro-demos.ipynb
new file mode 100644
index 0000000..ccef473
--- /dev/null
+++ b/lab-native-coros/native-coro-demos.ipynb
@@ -0,0 +1,400 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# The simplest native coroutine demo\n",
+ "(that I could imagine)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "import types\n",
+ "\n",
+ "@types.coroutine\n",
+ "def gen():\n",
+ " yield 42"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "async def delegating():\n",
+ " await gen()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The driving code starts here:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro = delegating()\n",
+ "coro"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "42"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro.send(None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# coro.send(None) # --> StopIteration"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# A slightly more interesting demo\n",
+ "Now the generator-coroutine yields 3 times."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "@types.coroutine\n",
+ "def gen123():\n",
+ " return (i for i in range(1, 4))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "async def delegating():\n",
+ " await gen123()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Driving code:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "1"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro = delegating()\n",
+ "coro.send(None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "2"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro.send(None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro.send(None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# coro.send(None) # --> StopIteration"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# coro.send(None) # --> RuntimeError"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# A generator-coroutine that receives values\n",
+ "The driving code can send values other than `None`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "import types\n",
+ "\n",
+ "@types.coroutine\n",
+ "def times10(terms):\n",
+ " n = yield 'Ready to begin!'\n",
+ " for _ in range(terms):\n",
+ " n = yield n * 10\n",
+ " return n * 10"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "async def delegating(terms):\n",
+ " res = await times10(terms)\n",
+ " return res"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Driving code must *prime* the coroutine by sending `None` initially:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Ready to begin!'"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro = delegating(3)\n",
+ "coro.send(None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "50"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro.send(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "60"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro.send(6)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "70"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "coro.send(7)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To retrieve the last result, we must catch `StopIteration` and get its `value` attribute:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "80"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "try:\n",
+ " coro.send(8)\n",
+ "except StopIteration as e:\n",
+ " res = e.value\n",
+ "res"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/spinner/spinner_await.py b/lab-spinner/spinner_await.py
similarity index 100%
rename from spinner/spinner_await.py
rename to lab-spinner/spinner_await.py
diff --git a/spinner/spinner_curio.py b/lab-spinner/spinner_curio.py
similarity index 100%
rename from spinner/spinner_curio.py
rename to lab-spinner/spinner_curio.py
diff --git a/spinner/spinner_thread.py b/lab-spinner/spinner_thread.py
similarity index 100%
rename from spinner/spinner_thread.py
rename to lab-spinner/spinner_thread.py
diff --git a/maps/files/.gitignore b/maps/files/.gitignore
new file mode 100644
index 0000000..a136337
--- /dev/null
+++ b/maps/files/.gitignore
@@ -0,0 +1 @@
+*.pdf
diff --git a/countries/downloads/FOR-DEMO b/maps/files/DIR_FOR_DOWNLOADS
similarity index 100%
rename from countries/downloads/FOR-DEMO
rename to maps/files/DIR_FOR_DOWNLOADS
diff --git a/maps/getmaps.py b/maps/getmaps.py
new file mode 100755
index 0000000..2725498
--- /dev/null
+++ b/maps/getmaps.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+
+"""
+Download Shanghai maps from http://media.lonelyplanet.com/ebookmaps/
+"""
+
+from concurrent import futures
+from urllib import request, error
+import os
+
+
+BASE_URL = 'https://media.lonelyplanet.com/ebookmaps/Shanghai/'
+LOCAL_DIR = 'files/'
+FILE_DATA = '''
+bund-overview.pdf (793 KB)
+bund-wt.pdf (374 KB)
+bund.pdf (1954 KB)
+century-ave.pdf (984 KB)
+city-overview.pdf (1928 KB)
+contents.pdf (233 KB)
+country-city-review-legend-asia-2C.pdf (455 KB)
+day-trips-loc.pdf (257 KB)
+french-east.pdf (1513 KB)
+french-overview.pdf (656 KB)
+french-west.pdf (1464 KB)
+french-wt.pdf (342 KB)
+hangzhou.pdf (2140 KB)
+hongkou-overview.pdf (768 KB)
+hongkou.pdf (2620 KB)
+jingan-overview.pdf (722 KB)
+jingan-wt.pdf (299 KB)
+north-jingan.pdf (1208 KB)
+old-town-overview.pdf (653 KB)
+old-town-wt.pdf (352 KB)
+old-town.pdf (2149 KB)
+pudong-overview.pdf (554 KB)
+pudong-wt.pdf (395 KB)
+pudong.pdf (1332 KB)
+shanghai-index.pdf (1594 KB)
+south-jingan.pdf (2254 KB)
+suzhou.pdf (1771 KB)
+west-shanghai-overview.pdf (971 KB)
+west-shanghai.pdf (3240 KB)
+xujiahui-overview.pdf (454 KB)
+xujiahui.pdf (1883 KB)
+'''
+
+
+def names():
+ for line in FILE_DATA.strip().split('\n'):
+ yield line.split()[0]
+
+
+def get_file(name, timeout):
+ """Download ans save a single file"""
+
+ url = BASE_URL + name
+
+ with request.urlopen(url, timeout=timeout) as conn:
+ data = conn.read()
+
+ with open(LOCAL_DIR + name, 'wb') as fp:
+ fp.write(data)
+
+ return (name, len(data))
+
+
+
+def main():
+ with futures.ThreadPoolExecutor() as executor:
+
+ downloads = [executor.submit(get_file, name, 60)
+ for name in names()]
+
+ for future in futures.as_completed(downloads):
+ try:
+ name, length = future.result()
+ except error.HTTPError as exc:
+ print(f'*** {exc} ({exc.url})')
+ else:
+ print(f'{length:9,d} bytes\t{name}')
+
+
+main()
diff --git a/requirements.txt b/requirements.txt
index 8128c50..738e9dc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,3 @@
-aiodns==1.1.1
-aiohttp==2.2.3
-aiomysql==0.0.9
-click==6.7
-cchardet==2.1.1
-pytest==3.1.3
-requests==2.18.2
-tqdm==4.15.0
+requests==2.22
+aiohttp==3.6.1
+curio==0.9
diff --git a/slides/Makefile b/slides/Makefile
new file mode 100644
index 0000000..01c541d
--- /dev/null
+++ b/slides/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = ModernPythonConcurrency
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/slides/conf.py b/slides/conf.py
new file mode 100644
index 0000000..1235114
--- /dev/null
+++ b/slides/conf.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Modern Python Concurrency documentation build configuration file, created by
+# sphinx-quickstart on Wed Aug 9 08:07:03 2017.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'Modern Python Concurrency'
+copyright = '2017, Luciano Ramalho'
+author = 'Luciano Ramalho'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '2017'
+# The full version, including alpha/beta/rc tags.
+release = '2017'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ModernPythonConcurrencydoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'ModernPythonConcurrency.tex', 'Modern Python Concurrency Documentation',
+ 'Luciano Ramalho', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'modernpythonconcurrency', 'Modern Python Concurrency Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'ModernPythonConcurrency', 'Modern Python Concurrency Documentation',
+ author, 'ModernPythonConcurrency', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+
diff --git a/slides/index.rst b/slides/index.rst
new file mode 100644
index 0000000..5bc912b
--- /dev/null
+++ b/slides/index.rst
@@ -0,0 +1,13 @@
+.. Modern Python Concurrency documentation master file, created by
+ sphinx-quickstart on Wed Aug 9 08:07:03 2017.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+=========================
+Modern Python Concurrency
+=========================
+
+.. toctree::
+
+ spinner
+ native-coro-demos
diff --git a/slides/make.bat b/slides/make.bat
new file mode 100644
index 0000000..dc41e51
--- /dev/null
+++ b/slides/make.bat
@@ -0,0 +1,36 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+set SPHINXPROJ=ModernPythonConcurrency
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/slides/native-coro-demos.rst b/slides/native-coro-demos.rst
new file mode 100644
index 0000000..e8c9f55
--- /dev/null
+++ b/slides/native-coro-demos.rst
@@ -0,0 +1,5 @@
+Native coroutine demos
+======================
+
+.. literalinclude:: ../native-coro-demos.py
+ :linenos:
diff --git a/slides/pybay-concur.key b/slides/pybay-concur.key
index ff5209f..2e207ee 100644
Binary files a/slides/pybay-concur.key and b/slides/pybay-concur.key differ
diff --git a/slides/pybay-concur.pdf b/slides/pybay-concur.pdf
new file mode 100644
index 0000000..0bb3132
Binary files /dev/null and b/slides/pybay-concur.pdf differ
diff --git a/slides/pybay-think-pythonista.key b/slides/pybay-think-pythonista.key
new file mode 100644
index 0000000..8516cbd
Binary files /dev/null and b/slides/pybay-think-pythonista.key differ
diff --git a/slides/pybay-think-pythonista.pdf b/slides/pybay-think-pythonista.pdf
new file mode 100644
index 0000000..0fa4d3b
Binary files /dev/null and b/slides/pybay-think-pythonista.pdf differ
diff --git a/slides/spinner.rst b/slides/spinner.rst
new file mode 100644
index 0000000..3f3ede1
--- /dev/null
+++ b/slides/spinner.rst
@@ -0,0 +1,26 @@
+Spinner examples
+================
+
+Thread
+------
+
+.. literalinclude:: ../spinner/spinner_thread.py
+ :linenos:
+
+yield-from
+----------
+
+.. literalinclude:: ../spinner/spinner_yield.py
+ :linenos:
+
+async/await
+-----------
+
+.. literalinclude:: ../spinner/spinner_await.py
+ :linenos:
+
+curio
+-----
+
+.. literalinclude:: ../spinner/spinner_curio.py
+ :linenos:
diff --git a/slides/tw-async-await.key b/slides/tw-async-await.key
new file mode 100644
index 0000000..91da822
Binary files /dev/null and b/slides/tw-async-await.key differ
diff --git a/spinner/spinner_yield.py b/spinner/spinner_yield.py
deleted file mode 100755
index 23d0322..0000000
--- a/spinner/spinner_yield.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/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
-
-import asyncio
-import itertools
-import sys
-
-
-@asyncio.coroutine # <1>
-def spin(msg):
- 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) # <2>
- except asyncio.CancelledError: # <3>
- break
- write(' ' * len(status) + '\x08' * len(status))
-
-
-@asyncio.coroutine # <4>
-def slow_function():
- # pretend waiting a long time for I/O
- yield from asyncio.sleep(3) # <5>
- return 42
-
-
-@asyncio.coroutine # <6>
-def supervisor(loop):
- spinner = loop.create_task(spin('thinking!')) # <7>
- print('spinner object:', spinner) # <8>
- result = yield from slow_function() # <9>
- spinner.cancel() # <10>
- return result
-
-
-def main():
- loop = asyncio.get_event_loop() # <11>
- result = loop.run_until_complete(supervisor(loop)) # <12>
- loop.close()
- print('Answer:', result)
-
-
-if __name__ == '__main__':
- main()
diff --git a/unicode/servers/signs_client.py b/unicode/servers/signs_client.py
new file mode 100755
index 0000000..02249be
--- /dev/null
+++ b/unicode/servers/signs_client.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+import sys
+
+import asyncio
+import aiohttp
+
+BASE_URL = 'http://localhost:8000/'
+
+
+async def get_chars(client, word):
+ url = '{}index/{}'.format(BASE_URL, word)
+ async with client.get(url) as resp:
+ assert resp.status == 200
+ text = await resp.text()
+ _, chars = text.split('\n', 1)
+ return set(chars.split())
+
+
+async def get_name(client, char, semaphore):
+ async with semaphore:
+ url = '{}name/U+{:04X}'.format(BASE_URL, ord(char))
+ async with client.get(url) as resp:
+ assert resp.status == 200
+ return await resp.text()
+
+
+async def query(loop, words):
+ async with aiohttp.ClientSession(loop=loop) as client:
+ to_do = [get_chars(client, word) for word in words]
+ res = await asyncio.gather(*to_do)
+ chars = set.intersection(*res)
+ semaphore = asyncio.Semaphore(2)
+ to_do = [get_name(client, char, semaphore) for char in chars]
+ names = await asyncio.gather(*to_do)
+ return names
+
+
+def main(args):
+ loop = asyncio.get_event_loop()
+ res = loop.run_until_complete(query(loop, args))
+ for line in sorted(res):
+ print(line)
+
+ loop.close()
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/unicode/servers/signs_server.py b/unicode/servers/signs_server.py
index 968684d..0f18cb3 100755
--- a/unicode/servers/signs_server.py
+++ b/unicode/servers/signs_server.py
@@ -12,12 +12,12 @@
PORT = 8000
+
async def usage(request):
text = (
'use\t/index/«word» to get list of characters '
'with «word» in their Unicode names\n'
'\t/name/«c» to get Unicode name of character «c»'
-
)
return web.Response(text=text)
@@ -39,14 +39,28 @@ async def index_for(request):
return web.Response(text=text)
+def parse_codepoint(codepoint):
+ codepoint = codepoint.replace('U+', '')
+ try:
+ return chr(int(codepoint, 16))
+ except ValueError:
+ raise web.HTTPBadRequest(
+ text='Invalid hex number after U+ codepoint prefix.')
+
+
async def char_name(request):
char = request.match_info.get('char', '')
+ if char.upper().startswith('U+'):
+ char = parse_codepoint(char)
if len(char) > 1:
- raise web.HTTPBadRequest(text='Only one character per request is allowed.')
+ raise web.HTTPBadRequest(
+ text='Only one character per request is allowed.')
name = unicodedata.name(char, None)
+ codepoint = 'U+{:04X}'.format(ord(char))
if name is None:
- raise web.HTTPNotFound(text='No name for character {} in Unicode 9.'.format(char))
- text = f'U+{ord(char):04x}\t{char}\t{name}'
+ raise web.HTTPNotFound(text=
+ 'No name for character {} in Unicode 9.'.format(codepoint))
+ text = '\t'.join([codepoint, name, char])
return web.Response(text=text)
@@ -74,6 +88,5 @@ def main(global_delay, local_delay, concurrency):
web.run_app(app, port=PORT)
-
if __name__ == '__main__':
main()