├── LICENSE ├── README.md ├── asyncio_gather.py ├── asyncio_iter.py ├── asyncio_recap.py ├── req_http.py ├── req_http_aio.py ├── sync_to_async_before.py ├── sync_to_async_correct.py └── sync_to_async_wrong.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ArjanCodes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asyncio 2 | 3 | If your software interacts with external APIs, you need to know concurrent programming. I show you how it works in Python and then take it to the next level by showing advanced concurrent features such as using async with generators and comprehensions, show a simple way to turn blocking code into concurrent code, and then I cover how concurrency affects software design and architecture. 4 | 5 | Video: https://youtu.be/GpqAQxH1Afc. 6 | -------------------------------------------------------------------------------- /asyncio_gather.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from random import randint 3 | from time import perf_counter 4 | from typing import Any, Awaitable 5 | 6 | from req_http import http_get, http_get_sync 7 | 8 | # The highest Pokemon id 9 | MAX_POKEMON = 898 10 | 11 | 12 | def get_random_pokemon_name_sync() -> str: 13 | pokemon_id = randint(1, MAX_POKEMON) 14 | pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" 15 | pokemon = http_get_sync(pokemon_url) 16 | return str(pokemon["name"]) 17 | 18 | 19 | async def get_random_pokemon_name() -> str: 20 | pokemon_id = randint(1, MAX_POKEMON) 21 | pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" 22 | pokemon = await http_get(pokemon_url) 23 | return str(pokemon["name"]) 24 | 25 | 26 | async def main() -> None: 27 | 28 | # synchronous call 29 | time_before = perf_counter() 30 | for _ in range(20): 31 | get_random_pokemon_name_sync() 32 | print(f"Total time (synchronous): {perf_counter() - time_before}") 33 | 34 | # asynchronous call 35 | time_before = perf_counter() 36 | await asyncio.gather(*[get_random_pokemon_name() for _ in range(20)]) 37 | print(f"Total time (asynchronous): {perf_counter() - time_before}") 38 | 39 | 40 | asyncio.run(main()) 41 | -------------------------------------------------------------------------------- /asyncio_iter.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from random import randint 3 | from typing import AsyncIterable 4 | 5 | from req_http import http_get 6 | 7 | # The highest Pokemon id 8 | MAX_POKEMON = 898 9 | 10 | 11 | async def get_random_pokemon_name() -> str: 12 | pokemon_id = randint(1, MAX_POKEMON) 13 | pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" 14 | pokemon = await http_get(pokemon_url) 15 | return str(pokemon["name"]) 16 | 17 | 18 | async def next_pokemon(total: int) -> AsyncIterable[str]: 19 | for _ in range(total): 20 | name = await get_random_pokemon_name() 21 | yield name 22 | 23 | 24 | async def main(): 25 | 26 | # retrieve the next 10 pokemon names 27 | async for name in next_pokemon(10): 28 | print(name) 29 | 30 | # asynchronous list comprehensions 31 | names = [name async for name in next_pokemon(10)] 32 | print(names) 33 | 34 | 35 | asyncio.run(main()) 36 | -------------------------------------------------------------------------------- /asyncio_recap.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from random import randint 3 | 4 | from req_http import JSONObject, http_get 5 | 6 | # The highest Pokemon id 7 | MAX_POKEMON = 898 8 | 9 | 10 | async def get_pokemon(pokemon_id: int) -> JSONObject: 11 | pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" 12 | return await http_get(pokemon_url) 13 | 14 | 15 | async def main() -> None: 16 | pokemon_id = randint(1, MAX_POKEMON) 17 | pokemon = await get_pokemon(pokemon_id + 1) 18 | print(pokemon["name"]) 19 | 20 | 21 | asyncio.run(main()) 22 | -------------------------------------------------------------------------------- /req_http.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import requests 4 | 5 | # A few handy JSON types 6 | JSON = int | str | float | bool | None | dict[str, "JSON"] | list["JSON"] 7 | JSONObject = dict[str, JSON] 8 | JSONList = list[JSON] 9 | 10 | 11 | def http_get_sync(url: str) -> JSONObject: 12 | response = requests.get(url) 13 | return response.json() 14 | 15 | 16 | async def http_get(url: str) -> JSONObject: 17 | return await asyncio.to_thread(http_get_sync, url) 18 | -------------------------------------------------------------------------------- /req_http_aio.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | 3 | # A few handy JSON types 4 | JSON = int | str | float | bool | None | dict[str, "JSON"] | list["JSON"] 5 | JSONObject = dict[str, JSON] 6 | JSONList = list[JSON] 7 | 8 | 9 | async def http_get(url: str) -> JSONObject: 10 | async with aiohttp.ClientSession() as session: 11 | async with session.get(url) as response: 12 | return await response.json() 13 | -------------------------------------------------------------------------------- /sync_to_async_before.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import requests 5 | 6 | 7 | async def counter(until: int = 10) -> None: 8 | now = time.perf_counter() 9 | print("Started counter") 10 | for i in range(0, until): 11 | last = now 12 | await asyncio.sleep(0.01) 13 | now = time.perf_counter() 14 | print(f"{i}: Was asleep for {now - last}s") 15 | 16 | 17 | def send_request(url: str) -> int: 18 | print("Sending HTTP request") 19 | response = requests.get(url) 20 | return response.status_code 21 | 22 | 23 | async def main() -> None: 24 | 25 | status_code = send_request("https://www.arjancodes.com") 26 | print(f"Got HTTP response with status {status_code}") 27 | 28 | await counter() 29 | 30 | 31 | asyncio.run(main()) 32 | -------------------------------------------------------------------------------- /sync_to_async_correct.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import requests 5 | 6 | 7 | async def counter(until: int = 10) -> None: 8 | now = time.perf_counter() 9 | print("Started counter") 10 | for i in range(0, until): 11 | last = now 12 | await asyncio.sleep(0.01) 13 | now = time.perf_counter() 14 | print(f"{i}: Was asleep for {now - last}s") 15 | 16 | 17 | def send_request(url: str) -> int: 18 | print("Sending HTTP request") 19 | response = requests.get(url) 20 | return response.status_code 21 | 22 | 23 | async def send_async_request(url: str) -> int: 24 | return await asyncio.to_thread(send_request, url) 25 | 26 | 27 | async def main() -> None: 28 | 29 | status_code, _ = await asyncio.gather( 30 | send_async_request("https://www.arjancodes.com"), counter() 31 | ) 32 | print(f"Got HTTP response with status {status_code}.") 33 | 34 | 35 | asyncio.run(main()) 36 | -------------------------------------------------------------------------------- /sync_to_async_wrong.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import requests 5 | 6 | 7 | async def counter(until: int = 10) -> None: 8 | now = time.perf_counter() 9 | print("Started counter") 10 | for i in range(0, until): 11 | last = now 12 | await asyncio.sleep(0.01) 13 | now = time.perf_counter() 14 | print(f"{i}: Was asleep for {now - last}s") 15 | 16 | 17 | def send_request(url: str) -> int: 18 | print("Sending HTTP request") 19 | response = requests.get(url) 20 | return response.status_code 21 | 22 | 23 | async def main() -> None: 24 | task = asyncio.create_task(counter()) 25 | 26 | status_code = send_request("https://www.arjancodes.com") 27 | print(f"Got HTTP response with status {status_code}") 28 | 29 | await task 30 | 31 | 32 | asyncio.run(main()) 33 | --------------------------------------------------------------------------------