├── .DS_Store ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── CH_01_04_begin.py ├── CH_01_04_end.py ├── CH_01_05.py ├── CH_01_06.py ├── CH_02_01_begin.py ├── CH_02_01_end.py ├── CH_02_02_client.py ├── CH_02_02_server.py ├── CH_02_03 ├── create_json.py └── show_repos.html ├── CH_02_04 ├── create_json.py ├── repo_data.json └── show_repos.html ├── CH_03_02.py ├── CH_03_03.py ├── CH_03_04.py ├── CH_04_03.py ├── CH_04_05.py ├── CH_04_06.py ├── NOTICE.md ├── README.md ├── dump.rdb ├── repo_data.json ├── requirements.txt ├── show_repos.html └── templates ├── base.html ├── chat.html └── chat_redis.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinkedInLearning/async-python-foundations-applied-concepts-2422322/753e508de6929528e1c13b7ac785820547742a06/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Copy To Branches 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | copy-to-branches: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - name: Copy To Branches Action 12 | uses: planetoftheweb/copy-to-branches@v1 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | .vscode/ 141 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | RUN sudo apt-get update && sudo apt-get install -y redis-server && sudo rm -rf /var/lib/apt/lists/* 3 | 4 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | 2 | image: 3 | file: .gitpod.Dockerfile 4 | 5 | tasks: 6 | - init: > 7 | python3 -m pip install --upgrade pip && 8 | pip3 install -r requirements.txt 9 | -------------------------------------------------------------------------------- /CH_01_04_begin.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | 4 | import click 5 | 6 | 7 | def sleep_and_print(seconds): 8 | print(f"starting {seconds} sleep 😴") 9 | time.sleep(seconds) 10 | print(f"finished {seconds} sleep ⏰") 11 | return seconds 12 | 13 | 14 | start = datetime.now() 15 | print([sleep_and_print(3), sleep_and_print(6)]) 16 | click.secho(f"{datetime.now()-start}", bold=True, bg="blue", fg="white") 17 | -------------------------------------------------------------------------------- /CH_01_04_end.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime 3 | import click 4 | 5 | 6 | async def sleep_and_print(seconds): 7 | print(f"starting async {seconds} sleep 😴") 8 | await asyncio.sleep(seconds) 9 | print(f"finished async {seconds} sleep ⏰") 10 | return seconds 11 | 12 | 13 | async def main(): 14 | # using arguments 15 | results = await asyncio.gather(sleep_and_print(3), sleep_and_print(6)) 16 | 17 | # building list 18 | # coroutines_list = [] 19 | # for i in range(1, 11): 20 | # coroutines_list.append(sleep_and_print(i)) 21 | # results = await asyncio.gather(*coroutines_list) 22 | print(results) 23 | 24 | 25 | start = datetime.now() 26 | asyncio.run(main()) 27 | click.secho(f"{datetime.now()-start}", bold=True, bg="blue", fg="white") 28 | -------------------------------------------------------------------------------- /CH_01_05.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime 3 | import click 4 | 5 | 6 | async def sleep_five(): 7 | pass # your code here 8 | 9 | 10 | async def sleep_three_then_five(): 11 | pass # your code here 12 | 13 | 14 | async def main(): 15 | results = "your code" 16 | print(results) 17 | 18 | 19 | start = datetime.now() 20 | asyncio.run(main()) 21 | click.secho(f"{datetime.now()-start}", bold=True, bg="blue", fg="white") 22 | -------------------------------------------------------------------------------- /CH_01_06.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from datetime import datetime 3 | import click 4 | 5 | 6 | async def sleep_five(): 7 | await asyncio.sleep(5) 8 | print("sleep five done") 9 | 10 | 11 | async def sleep_three_then_five(): 12 | await asyncio.sleep(3) 13 | await sleep_five() 14 | 15 | 16 | async def main(): 17 | await asyncio.gather(sleep_five(), sleep_three_then_five()) 18 | 19 | 20 | 21 | start = datetime.now() 22 | asyncio.run(main()) 23 | click.secho(f"{datetime.now()-start}", bold=True, bg="blue", fg="white") 24 | -------------------------------------------------------------------------------- /CH_02_01_begin.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pprint import pprint 3 | 4 | import click 5 | import requests 6 | 7 | urls = [ 8 | "http://httpbin.org/get?text=python", 9 | "http://httpbin.org/get?text=is", 10 | "http://httpbin.org/get?text=fun", 11 | "http://httpbin.org/get?text=and", 12 | "http://httpbin.org/get?text=useful", 13 | "http://httpbin.org/get?text=you", 14 | "http://httpbin.org/get?text=can", 15 | "http://httpbin.org/get?text=almost", 16 | "http://httpbin.org/get?text=do", 17 | "http://httpbin.org/get?text=anything", 18 | "http://httpbin.org/get?text=with", 19 | "http://httpbin.org/get?text=it", 20 | ] # 12 requests 21 | 22 | 23 | def get_args(url): 24 | return requests.get(url).json()["args"] 25 | 26 | 27 | start = datetime.now() 28 | pprint([get_args(url) for url in urls]) 29 | click.secho(f"{datetime.now()-start}", bold=True, bg="blue", fg="white") 30 | -------------------------------------------------------------------------------- /CH_02_01_end.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pprint import pprint 3 | import asyncio 4 | 5 | import aiohttp 6 | import click 7 | 8 | urls = [ 9 | "http://httpbin.org/get?text=python", 10 | "http://httpbin.org/get?text=is", 11 | "http://httpbin.org/get?text=fun", 12 | "http://httpbin.org/get?text=and", 13 | "http://httpbin.org/get?text=useful", 14 | "http://httpbin.org/get?text=you", 15 | "http://httpbin.org/get?text=can", 16 | "http://httpbin.org/get?text=almost", 17 | "http://httpbin.org/get?text=do", 18 | "http://httpbin.org/get?text=anything", 19 | "http://httpbin.org/get?text=with", 20 | "http://httpbin.org/get?text=it", 21 | ] 22 | 23 | 24 | async def fetch_args(session, url): 25 | async with session.get(url) as response: 26 | data = await response.json() 27 | return data["args"] 28 | 29 | 30 | async def main(): 31 | async with aiohttp.ClientSession() as session: 32 | # create a collection of coroutines(can be done with comprehension ) 33 | fetch_coroutines = [] 34 | for url in urls: 35 | fetch_coroutines.append(fetch_args(session, url)) 36 | # waik up coroutines with gather 37 | data = await asyncio.gather(*fetch_coroutines) 38 | pprint(data) 39 | 40 | 41 | start = datetime.now() 42 | asyncio.run(main()) 43 | click.secho(f"{datetime.now()-start}", bold=True, bg="blue", fg="white") 44 | -------------------------------------------------------------------------------- /CH_02_02_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import click 3 | 4 | import websockets 5 | 6 | 7 | def render_response(message): 8 | click.clear() 9 | if "BLASTOFF" in message: 10 | click.secho(message, blink=True, bold=True, fg="red") 11 | else: 12 | click.secho(message, bold=True, fg="blue") 13 | 14 | 15 | async def astronout(): 16 | uri = "ws://localhost:8765" 17 | async with websockets.connect(uri) as websocket: 18 | is_ready = input("Are you ready? ") 19 | 20 | await websocket.send(is_ready) 21 | async for message in websocket: 22 | render_response(message) 23 | if "BLASTOFF" in message: 24 | return 25 | 26 | 27 | async def main(): 28 | await asyncio.gather(astronout(), astronout(), astronout(), astronout()) 29 | 30 | 31 | asyncio.run(main()) 32 | -------------------------------------------------------------------------------- /CH_02_02_server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import websockets 3 | import click 4 | from contextlib import suppress 5 | 6 | 7 | async def blastoff(websocket): 8 | click.secho(">> begin blastoff") 9 | for i in range(25): 10 | await websocket.send(f"\n\n\n>> {' ' * i}🚀") 11 | await asyncio.sleep(0.03) 12 | for i in range(3): 13 | await asyncio.sleep(0.5) 14 | await websocket.send(f"\n\n\n>> 🚀🚀🚀🚀🚀🚀🚀🚀 BLASTOFF 🚀🚀🚀🚀🚀🚀🚀🚀 <<") 15 | 16 | 17 | async def huston(websocket, path): 18 | click.clear() 19 | async for message in websocket: 20 | if "yes" in message.lower(): 21 | click.secho(">> begin countdown") 22 | for i in reversed(range(1, 11)): 23 | await websocket.send(f"\n\n\n>> Taking off in: {i}") 24 | await asyncio.sleep(0.8) 25 | with suppress(Exception): 26 | await blastoff(websocket) 27 | 28 | 29 | PORT = 8765 30 | click.secho(f"--- listening for websocket connections on port: {PORT} ---") 31 | start_server = websockets.serve(huston, "localhost", PORT) 32 | 33 | asyncio.get_event_loop().run_until_complete(start_server) 34 | asyncio.get_event_loop().run_forever() 35 | -------------------------------------------------------------------------------- /CH_02_03/create_json.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | import asyncio 3 | import json 4 | 5 | import aiohttp 6 | 7 | URIS = ( 8 | "https://api.github.com/orgs/python", 9 | "https://api.github.com/orgs/django", 10 | "https://api.github.com/orgs/pallets", 11 | ) 12 | 13 | 14 | def write_to_file(data): 15 | with open("repo_data.json", "w") as jf: 16 | json.dump(data, jf) 17 | 18 | 19 | async def fetch(session, url): 20 | async with session.get(url) as response: 21 | data = await response.json() 22 | return {"name": data["name"], "avatar_url": data["avatar_url"]} 23 | 24 | 25 | async def main(): 26 | async with aiohttp.ClientSession() as session: 27 | """ 28 | TODO 29 | 1. use 'await asyncio.gather' and 'fetch' to get repo names and avatar_urls 30 | 2. use 'write_to_file' to create a json file with the results 31 | 3. text your code 32 | a. 'cd CH_02_03' 33 | b. 'python -m http.server' 34 | NOTE: Feel free to use CH_02_01_end.py for reference. 35 | """ 36 | data = "this is a place holder" 37 | 38 | asyncio.run(main()) 39 | -------------------------------------------------------------------------------- /CH_02_03/show_repos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 40 | 41 | -------------------------------------------------------------------------------- /CH_02_04/create_json.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | import asyncio 3 | import json 4 | 5 | import aiohttp 6 | 7 | URIS = ( 8 | "https://api.github.com/orgs/python", 9 | "https://api.github.com/orgs/django", 10 | "https://api.github.com/orgs/pallets", 11 | ) 12 | 13 | 14 | def write_to_file(data): 15 | with open("repo_data.json", "w") as jf: 16 | json.dump(data, jf) 17 | 18 | 19 | async def fetch(session, url): 20 | async with session.get(url) as response: 21 | data = await response.json() 22 | return {"name": data["name"], "avatar_url": data["avatar_url"]} 23 | 24 | 25 | async def main(): 26 | async with aiohttp.ClientSession() as session: 27 | """ 28 | TODO 29 | 1. use 'await asyncio.gather' and 'fetch' to get repo names and avatar_urls 30 | 2. use 'write_to_file' to create a json file with the results 31 | 3. text your code 32 | a. 'cd CH_02_03' 33 | b. 'python -m http.server' 34 | NOTE: Feel free to use CH_02_01_end.py for reference. 35 | """ 36 | data = await asyncio.gather(*[fetch(session, uri) for uri in URIS]) 37 | write_to_file(data) 38 | 39 | 40 | loop = asyncio.get_event_loop() 41 | loop.run_until_complete(main()) 42 | -------------------------------------------------------------------------------- /CH_02_04/repo_data.json: -------------------------------------------------------------------------------- 1 | [{"name": "Python", "avatar_url": "https://avatars.githubusercontent.com/u/1525981?v=4"}, {"name": "Django", "avatar_url": "https://avatars.githubusercontent.com/u/27804?v=4"}, {"name": "Pallets", "avatar_url": "https://avatars.githubusercontent.com/u/16748505?v=4"}] -------------------------------------------------------------------------------- /CH_02_04/show_repos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 39 | 40 | -------------------------------------------------------------------------------- /CH_03_02.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aioredis 3 | import click 4 | import json 5 | 6 | 7 | class Chat: 8 | def __init__(self, room_name): 9 | self.room_name = room_name 10 | 11 | async def start_db(self): 12 | self.redis = await aioredis.create_redis_pool("redis://localhost") 13 | await self.redis.set("room_name", self.room_name) 14 | 15 | async def save_message(self, message_dictionary): 16 | room_name = await self.redis.get("room_name") 17 | message_json = json.dumps(message_dictionary) 18 | await self.redis.rpush(room_name, message_json) 19 | 20 | async def clear_db(self): 21 | await self.redis.flushall() 22 | 23 | async def get_all_messages(self): 24 | room_name = await self.redis.get("room_name") 25 | message_jsons = await self.redis.lrange(room_name, 0, -1, encoding="utf-8") 26 | messages = [] 27 | for message in message_jsons: 28 | message_dictionary = json.loads(message) 29 | messages.append(message_dictionary) 30 | return messages 31 | 32 | 33 | async def main(): 34 | chat_db = Chat("messages") 35 | await chat_db.start_db() 36 | await chat_db.save_message({"handle": "first_user", "message": "hey"}) 37 | await chat_db.save_message({"handle": "first_user", "message": "hey"}) 38 | await chat_db.save_message({"handle": "second_user", "message": "What's up?"}) 39 | await chat_db.save_message({"handle": "first_user", "message": "all good!"}) 40 | 41 | chat_messages = await chat_db.get_all_messages() 42 | 43 | click.secho(f" Chat ", fg="cyan", bold=True, bg="yellow") 44 | for message in chat_messages: 45 | click.secho(f' {message["handle"]} | {message["message"]} ', fg="cyan") 46 | await chat_db.clear_db() 47 | 48 | asyncio.run(main()) 49 | -------------------------------------------------------------------------------- /CH_03_03.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | 4 | import click 5 | import aioredis 6 | 7 | class Chat: 8 | def __init__(self, room_name): 9 | self.room_name = room_name 10 | 11 | async def start_db(self): 12 | self.redis = await aioredis.create_redis_pool("redis://localhost") 13 | await self.redis.set("room_name", self.room_name) 14 | 15 | async def save_message(self, message_dictionary): 16 | room_name = await self.redis.get("room_name") 17 | message_json = json.dumps(message_dictionary) 18 | await self.redis.rpush(room_name, message_json) 19 | 20 | async def clear_db(self): 21 | await self.redis.flushall() 22 | 23 | async def get_all_messages(self): 24 | room_name = await self.redis.get("room_name") 25 | message_jsons = await self.redis.lrange(room_name, 0, -1, encoding="utf-8") 26 | messages = [] 27 | for message in message_jsons: 28 | message_dictionary = json.loads(message) 29 | messages.append(message_dictionary) 30 | return messages 31 | 32 | async def get_name(self): 33 | return # your code here make sure you use encoding="utf-8" 34 | 35 | 36 | async def main(): 37 | chat_db = Chat("messages") 38 | await chat_db.start_db() 39 | await chat_db.save_message({"handle": "first_user", "message": "hey"}) 40 | await chat_db.save_message({"handle": "first_user", "message": "hey"}) 41 | await chat_db.save_message({"handle": "second_user", "message": "What's up?"}) 42 | await chat_db.save_message({"handle": "first_user", "message": "all good!"}) 43 | 44 | chat_messages = await chat_db.get_all_messages() 45 | 46 | click.secho(f" Chat ", fg="cyan", bold=True, bg="yellow") 47 | for message in chat_messages: 48 | click.secho(f' {message["handle"]} | {message["message"]} ', fg="cyan") 49 | await chat_db.clear_db() 50 | 51 | asyncio.run(main()) -------------------------------------------------------------------------------- /CH_03_04.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | 4 | import click 5 | import aioredis 6 | 7 | class Chat: 8 | def __init__(self, room_name): 9 | self.room_name = room_name 10 | 11 | async def start_db(self): 12 | self.redis = await aioredis.create_redis_pool("redis://localhost") 13 | await self.redis.set("room_name", self.room_name) 14 | 15 | async def save_message(self, message_dictionary): 16 | room_name = await self.redis.get("room_name") 17 | message_json = json.dumps(message_dictionary) 18 | await self.redis.rpush(room_name, message_json) 19 | 20 | async def clear_db(self): 21 | await self.redis.flushall() 22 | 23 | async def get_all_messages(self): 24 | room_name = await self.redis.get("room_name") 25 | message_jsons = await self.redis.lrange(room_name, 0, -1, encoding="utf-8") 26 | messages = [] 27 | for message in message_jsons: 28 | message_dictionary = json.loads(message) 29 | messages.append(message_dictionary) 30 | return messages 31 | 32 | async def get_name(self): 33 | return await self.redis.get("room_name", encoding="utf-8") 34 | 35 | 36 | async def main(): 37 | chat_db = Chat("messages") 38 | await chat_db.start_db() 39 | await chat_db.save_message({"handle": "first_user", "message": "hey"}) 40 | await chat_db.save_message({"handle": "first_user", "message": "hey"}) 41 | await chat_db.save_message({"handle": "second_user", "message": "What's up?"}) 42 | await chat_db.save_message({"handle": "first_user", "message": "all good!"}) 43 | 44 | chat_messages = await chat_db.get_all_messages() 45 | chat_db_name = await chat_db.get_name() 46 | click.secho(f" {chat_db_name} ", fg="cyan", bold=True, bg="yellow") 47 | for message in chat_messages: 48 | click.secho(f' {message["handle"]} | {message["message"]} ', fg="cyan") 49 | await chat_db.clear_db() 50 | 51 | asyncio.run(main()) -------------------------------------------------------------------------------- /CH_04_03.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from quart import Quart, websocket, render_template 4 | from quart import g 5 | 6 | 7 | app = Quart(__name__) 8 | 9 | 10 | connections = set() 11 | 12 | 13 | @app.websocket("/ws") 14 | async def ws(): 15 | connections.add(websocket._get_current_object()) 16 | try: 17 | while True: 18 | message = await websocket.receive() 19 | send_coroutines = [connection.send(message) for connection in connections] 20 | await asyncio.gather(*send_coroutines) 21 | finally: 22 | connections.remove(websocket._get_current_object()) 23 | 24 | 25 | @app.route("/") 26 | async def chat(): 27 | return await render_template("chat.html") 28 | 29 | 30 | app.run(use_reloader=True, port=3000) 31 | -------------------------------------------------------------------------------- /CH_04_05.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | from pprint import pp 4 | 5 | from quart import Quart, websocket, render_template 6 | from quart import g 7 | 8 | from quart_redis import RedisHandler, get_redis 9 | 10 | app = Quart(__name__) 11 | 12 | app.config["REDIS_URI"] = "redis://localhost/" 13 | redis_handler = RedisHandler(app) 14 | connections = set() 15 | 16 | import json 17 | import asyncio 18 | 19 | import click 20 | import aioredis 21 | 22 | 23 | class Chat: 24 | def __init__(self, room_name): 25 | self.room_name = room_name 26 | 27 | async def start_db(self): 28 | self.redis = await aioredis.create_redis_pool("redis://localhost") 29 | await self.redis.set("room_name", self.room_name) 30 | 31 | async def save_message(self, message_json): 32 | room_name = await self.redis.get("room_name") 33 | await self.redis.rpush(room_name, message_json) 34 | 35 | async def clear_db(self): 36 | await self.redis.flushall() 37 | 38 | async def get_all_messages(self): 39 | room_name = await self.redis.get("room_name") 40 | message_jsons = await self.redis.lrange(room_name, 0, -1, encoding="utf-8") 41 | messages = [] 42 | for message in message_jsons: 43 | message_dictionary = json.loads(message) 44 | messages.append(message_dictionary) 45 | 46 | return messages 47 | 48 | async def get_name(self): 49 | return await self.redis.get("room_name", encoding="utf-8") 50 | 51 | 52 | chat_db = Chat("chat_room") 53 | 54 | 55 | @app.before_serving 56 | async def init_db(): 57 | await chat_db.start_db() 58 | 59 | 60 | @app.websocket("/ws") 61 | async def ws(): 62 | connections.add(websocket._get_current_object()) 63 | try: 64 | while True: 65 | message = await websocket.receive() 66 | # save the message 67 | send_coroutines = [connection.send(message) for connection in connections] 68 | await asyncio.gather(*send_coroutines) 69 | finally: 70 | connections.remove(websocket._get_current_object()) 71 | 72 | 73 | @app.route("/") 74 | async def chat(): 75 | redis = get_redis() 76 | messages = [] # replace the empty list with your code 77 | return await render_template("chat_redis.html", messages=messages) 78 | 79 | 80 | app.run(use_reloader=True, port=3000) 81 | -------------------------------------------------------------------------------- /CH_04_06.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | from pprint import pp 4 | 5 | from quart import Quart, websocket, render_template 6 | from quart import g 7 | 8 | from quart_redis import RedisHandler, get_redis 9 | 10 | app = Quart(__name__) 11 | 12 | app.config["REDIS_URI"] = "redis://localhost/" 13 | redis_handler = RedisHandler(app) 14 | connections = set() 15 | 16 | import json 17 | import asyncio 18 | 19 | import click 20 | import aioredis 21 | 22 | 23 | class Chat: 24 | def __init__(self, room_name): 25 | self.room_name = room_name 26 | 27 | async def start_db(self): 28 | self.redis = await aioredis.create_redis_pool("redis://localhost") 29 | await self.redis.set("room_name", self.room_name) 30 | 31 | async def save_message(self, message_json): 32 | room_name = await self.redis.get("room_name") 33 | await self.redis.rpush(room_name, message_json) 34 | 35 | async def clear_db(self): 36 | await self.redis.flushall() 37 | 38 | async def get_all_messages(self): 39 | room_name = await self.redis.get("room_name") 40 | message_jsons = await self.redis.lrange(room_name, 0, -1, encoding="utf-8") 41 | messages = [] 42 | for message in message_jsons: 43 | message_dictionary = json.loads(message) 44 | messages.append(message_dictionary) 45 | 46 | return messages 47 | 48 | async def get_name(self): 49 | return await self.redis.get("room_name", encoding="utf-8") 50 | 51 | 52 | chat_db = Chat("chat_room") 53 | 54 | 55 | @app.before_serving 56 | async def init_db(): 57 | await chat_db.start_db() 58 | 59 | 60 | @app.websocket("/ws") 61 | async def ws(): 62 | connections.add(websocket._get_current_object()) 63 | try: 64 | while True: 65 | message = await websocket.receive() 66 | await chat_db.save_message(message) 67 | send_coroutines = [connection.send(message) for connection in connections] 68 | await asyncio.gather(*send_coroutines) 69 | finally: 70 | connections.remove(websocket._get_current_object()) 71 | 72 | 73 | @app.route("/") 74 | async def chat(): 75 | redis = get_redis() 76 | messages = await chat_db.get_all_messages() 77 | return await render_template("chat_redis.html", messages=messages) 78 | 79 | 80 | app.run(use_reloader=True, port=3000) 81 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 LinkedIn Corporation 2 | All Rights Reserved. 3 | 4 | Licensed under the LinkedIn Learning Exercise File License (the "License"). 5 | See LICENSE in the project root for license information. 6 | 7 | ATTRIBUTIONS: 8 | 9 | redis 10 | https://github.com/redis/redis 11 | Copyright (c) 2006-2020, Salvatore Sanfilippo 12 | Licenes: BSD-3 13 | https://opensource.org/licenses/BSD-3-Clause 14 | 15 | Please note, this project may automatically load third party code from external 16 | repositories (for example, NPM modules, Composer packages, or other dependencies). 17 | If so, such third party code may be subject to other license terms than as set 18 | forth above. In addition, such third party code may also depend on and load 19 | multiple tiers of dependencies. Please review the applicable licenses of the 20 | additional dependencies. 21 | 22 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 23 | 24 | BSD-3 License: 25 | 26 | Copyright (c) 2006-2020, Salvatore Sanfilippo 27 | All rights reserved. 28 | 29 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 30 | 31 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 32 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 33 | * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 34 | 35 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Async Python Foundations: Applied Concepts 2 | This is the repository for the LinkedIn Learning course Async Python Foundations: Applied Concepts. The full course is available from [LinkedIn Learning][lil-course-url]. 3 | 4 | ![Async Python Foundations: Applied Concepts][lil-thumbnail-url] 5 | 6 | If you were cooking a multicourse meal, would you prep one thing at a time? Put bread in the oven, wait. Warm the soup on the stove, wait. Then the main course. Wouldn’t it be more efficient to spend time prepping other food instead of waiting on tasks that don’t need your immediate attention? In the same way that having multiple things happen at the same time leads to faster meal prep, having multiple things happen in Python—or using an asynchronous approach—can be leveraged to boost application performance and make your Python programs extremely efficient. In this course, Ronnie Sheer gives you the tools to use async Python to solve real-world problems, gain familiarity with the async Python ecosystem, complete challenges with working examples, and become a more attractive candidate for engineering positions. If you’re an experienced Python user looking to take async Python from theory to practice, check out this hands-on course. 7 | 8 | ## Easy setup with Gitpod (cloud workspace). 9 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#/https://github.com/LinkedInLearning/async-python-foundations-applied-concepts-2422322) 10 | 11 | ## Local setup 12 | ## Downloads 13 | * Visit [python.org](https://www.python.org/) and download Python for your operating system. 14 | * Visit [redis.io](https://redis.io/download) and download Redis for your operating system. 15 | 16 | ## Setting up a virtual environment 17 | 1. Create a virtual environment with the following command: 18 | ```bash 19 | python3 -m venv venv 20 | ``` 21 | 1. Activate the virtual environment. 22 | 1. Linux and Mac 23 | ```bash 24 | source venv/bin/activate 25 | ``` 26 | 2. Windows 27 | ```bash 28 | venv\Scripts\activate 29 | ``` 30 | > :warning: *If you encounter an error using Windows PowerShell, try opening another shell with the "Run as Administrator" option.* 31 | 1. Install the required packages. 32 | ``` 33 | pip install -r requirements.txt 34 | ``` 35 | 36 | ## Every time you start a terminal session 37 | 1. Navigate to the course directory. 38 | 39 | 1. Activate the virtual environment. 40 | 1. Linux and Mac 41 | ```bash 42 | source venv/bin/activate 43 | ``` 44 | 2. Windows 45 | ```bash 46 | venv\Scripts\activate 47 | ``` 48 | 49 | ## Running a single exercise file 50 | > :warning: *Make sure that you have set up your environment according to the instructions above.* 51 | 52 | 1. Python 53 | ```bash 54 | python CH_01_04_end.py 55 | ``` 56 | 1. Starting redis(in separate terminal) 57 | ```bash 58 | redis-server 59 | ``` 60 | 61 | 62 | ### Instructor 63 | 64 | Ronnie Sheer 65 | 66 | Check out my other courses on [LinkedIn Learning](https://www.linkedin.com/learning/instructors/ronnie-sheer). 67 | 68 | [lil-course-url]: https://www.linkedin.com/learning/async-python-foundations-applied-concepts 69 | [lil-thumbnail-url]: https://cdn.lynda.com/course/2422322/2422322-1630605594091-16x9.jpg 70 | 71 | -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinkedInLearning/async-python-foundations-applied-concepts-2422322/753e508de6929528e1c13b7ac785820547742a06/dump.rdb -------------------------------------------------------------------------------- /repo_data.json: -------------------------------------------------------------------------------- 1 | [{"name": "Python", "avatar_url": "https://avatars.githubusercontent.com/u/1525981?v=4"}, {"name": "Django", "avatar_url": "https://avatars.githubusercontent.com/u/27804?v=4"}, {"name": "Pallets", "avatar_url": "https://avatars.githubusercontent.com/u/16748505?v=4"}] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==0.7.0 2 | aiohttp==3.7.4.post0 3 | aioredis==1.3.1 4 | appdirs==1.4.4 5 | async-timeout==3.0.1 6 | attrs==21.1.0 7 | black==21.5b1 8 | blinker==1.4 9 | certifi==2021.5.30 10 | chardet==4.0.0 11 | click==7.1.2 12 | Flask==2.0.0 13 | h11==0.12.0 14 | h2==4.0.0 15 | hiredis==2.0.0 16 | hpack==4.0.0 17 | Hypercorn==0.11.2 18 | hyperframe==6.0.1 19 | idna==2.10 20 | itsdangerous==2.0.1 21 | Jinja2==3.0.1 22 | MarkupSafe==2.0.1 23 | multidict==5.1.0 24 | mypy-extensions==0.4.3 25 | pathspec==0.8.1 26 | priority==1.3.0 27 | Quart==0.15.0 28 | quart-redis==0.1.0 29 | regex==2021.4.4 30 | requests==2.25.1 31 | toml==0.10.2 32 | typing-extensions==3.10.0.0 33 | websockets==9.1 34 | urllib3==1.26.6 35 | Werkzeug==2.0.1 36 | wsproto==1.0.0 37 | yarl==1.6.3 38 | -------------------------------------------------------------------------------- /show_repos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /templates/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Chat App 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

Chat

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 62 | 63 | -------------------------------------------------------------------------------- /templates/chat_redis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Chat App 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

Chat

20 |
21 | 22 | {% for message in messages %} 23 | 24 | 25 | 26 | 27 | {% else %} 28 | {% endfor %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 71 | 72 | --------------------------------------------------------------------------------
{{message.handle }}{{message.message }}