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()