diff --git a/.gitignore b/.gitignore index 178dc1f..ab5e453 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ **/*.pyc -**/venv/* +**/*venv/* **/.idea/* diff --git a/source-code/chapter-4/exercise-1/docker-hello-world/Dockerfile b/source-code/chapter-4/exercise-1/docker-hello-world/Dockerfile index 7c1d5ab..620cf40 100644 --- a/source-code/chapter-4/exercise-1/docker-hello-world/Dockerfile +++ b/source-code/chapter-4/exercise-1/docker-hello-world/Dockerfile @@ -1,9 +1,8 @@ FROM python:3-alpine -LABEL author="sathyabhat" LABEL description="Dockerfile for Python script which prints Hello, Name" COPY hello-world.py /app/ -ENV NAME=Sathya +ENV NAME=Readers CMD python3 /app/hello-world.py diff --git a/source-code/chapter-4/exercise-1/docker-hello-world/hello-world.py b/source-code/chapter-4/exercise-1/docker-hello-world/hello-world.py index 850eed0..49e450d 100644 --- a/source-code/chapter-4/exercise-1/docker-hello-world/hello-world.py +++ b/source-code/chapter-4/exercise-1/docker-hello-world/hello-world.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 - from os import getenv if getenv('NAME') is None: name = 'World' else: name = getenv('NAME') - -print("Hello, {}!".format(name)) +print(f"Hello, {name}!") diff --git a/source-code/chapter-4/exercise-2/README.md b/source-code/chapter-4/exercise-2/README.md index 719d4d5..d3e955f 100644 --- a/source-code/chapter-4/exercise-2/README.md +++ b/source-code/chapter-4/exercise-2/README.md @@ -1,3 +1,6 @@ ### README -This exercise contains the source code for the second exercise of chapter 4. In this exercise, you will build two Docker images, the first one using a standard build process using python:3 as the base image. +This exercise contains the source code for the second exercise of chapter 4. In this exercise, you will build two Docker images + + - Using the standard build process using python:3 as the base image (present in [docker-multi-stage/standard-build](docker-multi-stage/standard-build) directory. + - Using Multi-Stage builds (present [docker-multi-stage/multistage-build](docker-multi-stage/multistage-build) directory. diff --git a/source-code/chapter-4/exercise-2/docker-multi-stage/multistage-build/requirements.txt b/source-code/chapter-4/exercise-2/docker-multi-stage/multistage-build/requirements.txt index 6c81bc0..ffc6560 100644 --- a/source-code/chapter-4/exercise-2/docker-multi-stage/multistage-build/requirements.txt +++ b/source-code/chapter-4/exercise-2/docker-multi-stage/multistage-build/requirements.txt @@ -1 +1 @@ -praw +praw==3.6.0 \ No newline at end of file diff --git a/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/Dockerfile b/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/Dockerfile index 602dc00..b084ae9 100644 --- a/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/Dockerfile +++ b/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/Dockerfile @@ -1,4 +1,3 @@ FROM python:3 COPY requirements.txt . RUN pip install -r requirements.txt - diff --git a/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/requirements.txt b/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/requirements.txt index 6c81bc0..ffc6560 100644 --- a/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/requirements.txt +++ b/source-code/chapter-4/exercise-2/docker-multi-stage/standard-build/requirements.txt @@ -1 +1 @@ -praw +praw==3.6.0 \ No newline at end of file diff --git a/source-code/chapter-4/exercise-3/README.md b/source-code/chapter-4/exercise-3/README.md index 6c776ec..82bdd0b 100644 --- a/source-code/chapter-4/exercise-3/README.md +++ b/source-code/chapter-4/exercise-3/README.md @@ -1,3 +1,20 @@ ### README -This exercise contains the source code for the third exercise of chapter 4. In this exercise, we’ll try writing the Dockerfile for the project. +This exercise contains the source code for the third exercise of chapter 4. In this exercise, we compose a Dockerfile for Newsbot and then use the Dockerfile to build a Docker image and run the container. + + +### Building the Docker image + +Build the image using the below command + +``` +docker build -t sathyabhat/newsbot +``` + +Run the container using + +``` +docker run -e NBT_ACCESS_TOKEN= sathyabhat/newsbot +``` + +Replace `` with the Telegram API Token that was generated. \ No newline at end of file diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/main.py b/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/main.py deleted file mode 100644 index bacc165..0000000 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/main.py +++ /dev/null @@ -1,22 +0,0 @@ -from flask import Flask -from newsbot import * -from states import States - -bot = Flask(__name__) - - -@bot.route('/index') -def index(): - return 'Thou shalt not pass!' - - -@bot.route('/telegram-update', methods=['POST']) -def telegram_update(): - handle_incoming_messages(States.last_updated_id) - - -if __name__ == '__main__': - States.last_updated_id = get_last_updated() - bot.run() - - diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/reddit.py b/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/reddit.py deleted file mode 100644 index 2ccb2c8..0000000 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/reddit.py +++ /dev/null @@ -1,48 +0,0 @@ -import praw -from states import log -from constants import REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET - -__author__ = 'Sathyajith' - - -def summarize(url): - log.info('Not yet implemented!') - return url - - -def get_latest_news(sub_reddits): - log.debug('Fetching news from reddit') - no_tokens_message = ( - "Reddit client id is not set, please create a client id as mentioned in" - " https://github.com/reddit-archive/reddit/wiki/OAuth2-Quick-Start-Example#first-steps" - " and set the environment variable `{}` similar to how `NBT_ACCESS_TOKEN` was done " - ) - - if REDDIT_CLIENT_ID is None: - return no_tokens_message.format("REDDIT_CLIENT_ID") - if REDDIT_CLIENT_SECRET is None: - return no_tokens_message.format("REDDIT_CLIENT_SECRET") - r = praw.Reddit(user_agent='SubReddit Newsfetcher Bot', client_id=REDDIT_CLIENT_ID, client_secret=REDDIT_CLIENT_SECRET) - # Can change the subreddit or add more. - sub_reddits = clean_up_subreddits(sub_reddits) - log.debug('Fetching subreddits: {0}'.format(sub_reddits)) - submissions = r.subreddit(sub_reddits).hot(limit=5) - submission_content = '' - try: - for post in submissions: - submission_content += summarize(post.title + ' - ' + post.url) + '\n' - except praw.errors.Forbidden: - log.debug('subreddit {0} is private'.format(sub_reddits)) - submission_content = "Sorry couldn't fetch; subreddit is private" - except praw.errors.InvalidSubreddit: - log.debug('Subreddit {} is invalid or doesn''t exist.'.format(sub_reddits)) - submission_content = "Sorry couldn't fetch; subreddit doesn't seem to exist" - except praw.errors.NotFound : - log.debug('Subreddit {} is invalid or doesn''t exist.'.format(sub_reddits)) - submission_content = "Sorry couldn't fetch; something went wrong, please do send a report to @sathyabhat" - return submission_content - - -def clean_up_subreddits(sub_reddits): - log.debug('Got subreddits to clean: {0}'.format(sub_reddits)) - return sub_reddits.strip().replace(" ", "").replace(',', '+') diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/requirements.txt b/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/requirements.txt deleted file mode 100644 index 6c81bc0..0000000 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -praw diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/Dockerfile b/source-code/chapter-4/exercise-3/newsbot/Dockerfile similarity index 55% rename from source-code/chapter-4/exercise-3/docker-subreddit-fetcher/Dockerfile rename to source-code/chapter-4/exercise-3/newsbot/Dockerfile index f3c594c..b0f0486 100644 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/Dockerfile +++ b/source-code/chapter-4/exercise-3/newsbot/Dockerfile @@ -1,9 +1,5 @@ FROM python:3-alpine - -COPY * /apps/subredditfetcher/ WORKDIR /apps/subredditfetcher/ +COPY . . RUN ["pip", "install", "-r", "requirements.txt"] - -ENV NBT_ACCESS_TOKEN="495637361:AAHIhiDTX1UeX17KJy0-FsMZEqEtCFYfcP8" - CMD ["python", "newsbot.py"] diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/LICENSE b/source-code/chapter-4/exercise-3/newsbot/LICENSE similarity index 100% rename from source-code/chapter-4/exercise-3/docker-subreddit-fetcher/LICENSE rename to source-code/chapter-4/exercise-3/newsbot/LICENSE diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/constants.py b/source-code/chapter-4/exercise-3/newsbot/constants.py similarity index 58% rename from source-code/chapter-4/exercise-3/docker-subreddit-fetcher/constants.py rename to source-code/chapter-4/exercise-3/newsbot/constants.py index 61174bd..092cde6 100644 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/constants.py +++ b/source-code/chapter-4/exercise-3/newsbot/constants.py @@ -1,13 +1,15 @@ __author__ = 'Sathyajith' from os import environ +from sys import exit ERR_NO_SOURCE = 'No sources defined! Set a source using /source list, of, sub, reddits' skip_list = [] sources_dict = {} +UPDATE_PERIOD = 1 +FALSE_RESPONSE = {"ok": False} BOT_KEY = environ.get('NBT_ACCESS_TOKEN') -REDDIT_CLIENT_ID = environ.get('REDDIT_CLIENT_ID') -REDDIT_CLIENT_SECRET = environ.get('REDDIT_CLIENT_SECRET') -API_BASE = 'https://api.telegram.org/bot' -UPDATE_PERIOD = 6 -FALSE_RESPONSE = {"ok": False} +if not BOT_KEY: + print("Telegram access token not set, exiting.") + exit(1) +API_BASE = f'https://api.telegram.org/bot{BOT_KEY}' \ No newline at end of file diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/newsbot.py b/source-code/chapter-4/exercise-3/newsbot/newsbot.py similarity index 80% rename from source-code/chapter-4/exercise-3/docker-subreddit-fetcher/newsbot.py rename to source-code/chapter-4/exercise-3/newsbot/newsbot.py index 55216e6..e960170 100644 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/newsbot.py +++ b/source-code/chapter-4/exercise-3/newsbot/newsbot.py @@ -12,15 +12,15 @@ def get_last_updated(): f.close() except FileNotFoundError: last_updated = 0 - log.debug('Last updated id: {0}'.format(last_updated)) + log.debug(f"Last updated id: {last_updated}") return last_updated if __name__ == '__main__': try: - log.debug('Starting up') + log.info("Starting up") States.last_updated = get_last_updated() while True: handle_incoming_messages(States.last_updated) except KeyboardInterrupt: - log.info('Received KeybInterrupt, exiting') \ No newline at end of file + log.info("Received KeybInterrupt, exiting") \ No newline at end of file diff --git a/source-code/chapter-4/exercise-3/newsbot/reddit.py b/source-code/chapter-4/exercise-3/newsbot/reddit.py new file mode 100644 index 0000000..0533c49 --- /dev/null +++ b/source-code/chapter-4/exercise-3/newsbot/reddit.py @@ -0,0 +1,32 @@ +import praw +from states import log + +__author__ = 'Sathyajith' + + +def get_latest_news(sub_reddits): + log.debug('Fetching news from reddit') + r = praw.Reddit(user_agent='Practical Docker With Python tutorial') + # Can change the subreddit or add more. + sub_reddits = clean_up_subreddits(sub_reddits) + log.debug(f"Fetching subreddits: {sub_reddits}") + submissions = r.get_subreddit(sub_reddits).get_top(limit=5) + submission_content = '' + try: + for post in submissions: + submission_content += f"{post.title} - {post.url} \n\n" + except praw.errors.Forbidden: + log.info(f"subreddit {sub_reddits} is private".format()) + submission_content = "Sorry couldn't fetch; subreddit is private" + except praw.errors.InvalidSubreddit: + log.info(f"Subreddit {sub_reddits} is invalid or doesn''t exist.") + submission_content = "Sorry couldn't fetch; subreddit doesn't seem to exist" + except praw.errors.NotFound : + log.info(f"Subreddit {sub_reddits} is invalid or doesn''t exist.") + submission_content = "Sorry couldn't fetch; something went wrong, please do send a report to @sathyabhat" + return submission_content + + +def clean_up_subreddits(sub_reddits): + log.debug('Got subreddits to clean: {0}'.format(sub_reddits)) + return sub_reddits.strip().replace(" ", "").replace(',', '+') diff --git a/source-code/chapter-4/exercise-3/newsbot/requirements.txt b/source-code/chapter-4/exercise-3/newsbot/requirements.txt new file mode 100644 index 0000000..11deb7e --- /dev/null +++ b/source-code/chapter-4/exercise-3/newsbot/requirements.txt @@ -0,0 +1,2 @@ +praw==3.6.0 +requests==2.20.0 diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/states.py b/source-code/chapter-4/exercise-3/newsbot/states.py similarity index 100% rename from source-code/chapter-4/exercise-3/docker-subreddit-fetcher/states.py rename to source-code/chapter-4/exercise-3/newsbot/states.py diff --git a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/telegram.py b/source-code/chapter-4/exercise-3/newsbot/telegram.py similarity index 76% rename from source-code/chapter-4/exercise-3/docker-subreddit-fetcher/telegram.py rename to source-code/chapter-4/exercise-3/newsbot/telegram.py index 2991d42..f7fb8e5 100644 --- a/source-code/chapter-4/exercise-3/docker-subreddit-fetcher/telegram.py +++ b/source-code/chapter-4/exercise-3/newsbot/telegram.py @@ -10,9 +10,9 @@ def get_updates(last_updated): - log.debug('Checking for requests, last updated passed is: {}'.format(last_updated)) + log.debug('Checking for requests, last updated passed is: {last_updated}') sleep(UPDATE_PERIOD) - response = requests.get(API_BASE + BOT_KEY + '/getUpdates', params={'offset': last_updated+1}) + response = requests.get(f"{API_BASE}/getUpdates", params={'offset': last_updated+1}) json_response = FALSE_RESPONSE if response.status_code != 200: # wait for a bit, try again @@ -23,14 +23,14 @@ def get_updates(last_updated): except ValueError: sleep(UPDATE_PERIOD*20) get_updates(last_updated) - log.info('received response: {}'.format(json_response)) + log.info(f"received response: {json_response}") return json_response def post_message(chat_id, text): - log.info('posting {} to {}'.format(text, chat_id)) + log.debug(f"posting {text} to {chat_id}") payload = {'chat_id': chat_id, 'text': text} - requests.post(API_BASE + BOT_KEY + '/sendMessage', data=payload) + requests.post(f"{API_BASE}/sendMessage", data=payload) def handle_incoming_messages(last_updated): @@ -38,7 +38,10 @@ def handle_incoming_messages(last_updated): split_chat_text = [] if r['ok']: for req in r['result']: - chat_sender_id = req['message']['chat']['id'] + if 'message' in req: + chat_sender_id = req['message']['chat']['id'] + else: + chat_sender_id = req['edited_message']['chat']['id'] try: chat_text = req['message']['text'] split_chat_text = chat_text.split() @@ -46,23 +49,23 @@ def handle_incoming_messages(last_updated): chat_text = '' split_chat_text.append(chat_text) log.debug('Looks like no chat text was detected... moving on') - try: + + if 'message' in req: person_id = req['message']['from']['id'] - except KeyError: - pass + else: + person_id = req['edited_message']['from']['id'] - log.info('Chat text received: {0}'.format(chat_text)) + log.info(f"Chat text received: {chat_text}") r = re.search('(source+)(.*)', chat_text) if (r is not None and r.group(1) == 'source'): if r.group(2): sources_dict[person_id] = r.group(2) - log.debug('Sources set for {0} to {1}'.format(sources_dict[person_id], r.group(2))) - post_message(person_id, 'Sources set as {0}!'.format(r.group(2))) + post_message(person_id, f"Sources set as {r.group(2)}!") else: post_message(person_id, 'We need a comma separated list of subreddits! No subreddit, no news :-(') if chat_text == '/stop': - log.debug('Added {0} to skip list'.format(chat_sender_id)) + log.debug(f"Added {chat_sender_id} to skip list") skip_list.append(chat_sender_id) post_message(chat_sender_id, "Ok, we won't send you any more messages.") @@ -89,5 +92,5 @@ def handle_incoming_messages(last_updated): f.write(str(last_updated)) States.last_updated = last_updated log.debug( - 'Updated last_updated to {0}'.format(last_updated)) + f'Updated last_updated to {last_updated}') f.close()