From 33f85849e4ce20d1a335fbd7f3068a69df09064a Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Fri, 8 Nov 2019 16:28:45 +0300 Subject: [PATCH 01/10] Iteartion I initial commit --- final_task/rss_reader/rss_reader.py | 142 ++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index e69de29..a90b02f 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -0,0 +1,142 @@ +import feedparser +import datetime +import argparse +import json +from bs4 import BeautifulSoup + +DEFAULT_LIMIT_COUNT = 3 +VERSION = '1.0' + + +class RSSReader: + is_verbose = False # logging is disabled by default + is_json = False + limit = DEFAULT_LIMIT_COUNT + + def __init__(self, url): + self.url = url + + def url_parsing(self): + parsed_url = feedparser.parse(self.url) + if parsed_url.status != 200: # URL-access check + print("Can't load RSS feed.") + raise Exception('RSS-reader failed. Status: {}.'.format(parsed_url.status)) + else: + self.log('RSS parsed successfully') + return parsed_url + + def information_about_site(self, parsed_url): + """Function which make dictionary with data of website""" + return { + 'Feed': parsed_url.feed['title'], + 'Date of access': parsed_url.updated, + 'Version': parsed_url.version, + } + + def make_news_data(self, news): + """ + Function which parsed HTML summary of news and make dictionary with all the necessary news data. + :return dictionary + """ + bs = BeautifulSoup(news['summary'], 'html.parser') + img = bs.find_all('img') + image_data = 'No image' + if len(img) > 0: + image_data = '{}\nSource of image: {}'.format(img[0]['alt'], img[0]['src']) + news_summary = { + 'Title': news['title'], + 'Date': news['published'], + 'Summary': bs.get_text(), + 'Link': news['link'], + 'Image': image_data, + } + return news_summary + + def news_data_collection(self, parsed_url): + """Function that collects data from all news by calling make_news_data. + :return string of dictionaries + """ + all_information_about_news = parsed_url.entries + # amount_of_news = len(parsed_url.entries) + all_news = [] + self.log('News gathering') + for news in all_information_about_news[:self.limit]: + try: # Exception when news is failed + dictionary_of_news_data = self.make_news_data(news) + except Exception as ex: + self.log("Error processing news: {} {}".format(type(ex), ex)) + continue # continues to process the following news + else: + all_news.append(dictionary_of_news_data) # make list of dictionaries of news data for optional output + return all_news + + def log(self, message): + """Print time and log message if verbose is active.""" + if self.is_verbose: + print(datetime.datetime.now(), message) + + def parse_to_json(self, dictionary): + return json.dumps(dictionary, indent=4) + + def output(self, about_website, all_news): + """Function which print information about site and a set of news.""" + print() + for key, value in about_website.items(): + print(key, ': ', value) + print() + for number_of_news in all_news: + print() + for key, value in number_of_news.items(): + print(key, ': ', value) + + def output_json(self, about_website, all_news): + print(self.parse_to_json([about_website] + all_news)) + + def doing_everything_in_class(self): + """Get news""" + self.log('Start parsing') + try: + parsed_url = self.url_parsing() + about_website = self.information_about_site(parsed_url) + except Exception as ex: # for any exception after website parsing + self.log("Error reading site data: {}, {}".format(type(ex), ex)) + return + string_of_news_dictionaries = self.news_data_collection(parsed_url) + if self.is_json: + self.log('Convert to JSON-format') + self.output_json(about_website, string_of_news_dictionaries) + else: + self.log('Output news') + self.output(about_website, string_of_news_dictionaries) + + +def arg_parse(): + """Function which parsed command-line arguments.""" + parser = argparse.ArgumentParser(description='Pure Python command-line RSS reader.') + parser.add_argument("source", type=str, + help="RSS URL") + parser.add_argument("--limit", type=int, default=DEFAULT_LIMIT_COUNT, + help="Limit news topics if this parameter provided") + parser.add_argument("--version", action="store_true", + help="Print version info") + parser.add_argument("--json", action="store_true", + help="Print result as JSON in stdout") + parser.add_argument("--verbose", action="store_true", + help="Outputs verbose status messages") + return parser.parse_args() + + +def main(): + args = arg_parse() + if args.version: + print('RSS-reader version {}'.format(VERSION)) # program version call + return + reader = RSSReader(args.source) + reader.limit = args.limit + reader.is_verbose = args.verbose + reader.is_json = args.json + reader.doing_everything_in_class() + + +if __name__ == '__main__': + main() From 8377deddb453d863998258cfa9552547608652e5 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Fri, 8 Nov 2019 20:53:59 +0300 Subject: [PATCH 02/10] Iteration I: adds unittests --- .gitignore | 50 +++++++++++++++++ final_task/README.md | 38 +++++++++++-- final_task/rss_reader/requirements.txt | 2 + final_task/rss_reader/rss_reader.py | 15 +++--- final_task/tests/RSS-unittest.py | 75 ++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 final_task/tests/RSS-unittest.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88315da --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ \ No newline at end of file diff --git a/final_task/README.md b/final_task/README.md index 7af281f..1d6908c 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -1,3 +1,35 @@ -# Your readme here -Some text. -Checkout how to write this file using *markdown*. +#The best RSS-reader! +Program is named rss_reader.py. + +To run the program from the command line, you must write the file name and string with URL-address of news site. + +Also it have such parameters like: +* --version (Print version info); +* --json (Print result as JSON in stdout); +* --verbose (Outputs verbose status messages); +* --limit LIMIT (Limit news topics if this parameter provided, type(LIMIT)=int). + +It's print a brief description of the news information that appeared on the news site in human-readable +format. + +JSON format example: + +```json +[ + { + "Feed": "Yahoo", + "Updated": "Fri, 08 Nov 2019 17:04:06 GMT", + "Version": "rss20" + }, + { + "Title": "Some title", + "Date": "Thu, 07 Nov 2019 09:59:52 -0500", + "Summary": "Some summary", + "Link": "Link to news", + "Image": "Some image information" + } +] +``` + +The following external libraries are used in this program: +feedparser and bs4. diff --git a/final_task/rss_reader/requirements.txt b/final_task/rss_reader/requirements.txt index e69de29..f57478b 100644 --- a/final_task/rss_reader/requirements.txt +++ b/final_task/rss_reader/requirements.txt @@ -0,0 +1,2 @@ +bs4 +feedparser diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index a90b02f..a2b0053 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -1,4 +1,5 @@ import feedparser +import sys import datetime import argparse import json @@ -28,9 +29,9 @@ def url_parsing(self): def information_about_site(self, parsed_url): """Function which make dictionary with data of website""" return { - 'Feed': parsed_url.feed['title'], - 'Date of access': parsed_url.updated, - 'Version': parsed_url.version, + 'Feed': parsed_url['feed']['title'], + 'Updated': parsed_url['updated'], + 'Version': parsed_url['version'], } def make_news_data(self, news): @@ -56,7 +57,7 @@ def news_data_collection(self, parsed_url): """Function that collects data from all news by calling make_news_data. :return string of dictionaries """ - all_information_about_news = parsed_url.entries + all_information_about_news = parsed_url['entries'] # amount_of_news = len(parsed_url.entries) all_news = [] self.log('News gathering') @@ -110,7 +111,7 @@ def doing_everything_in_class(self): self.output(about_website, string_of_news_dictionaries) -def arg_parse(): +def arg_parse(args): """Function which parsed command-line arguments.""" parser = argparse.ArgumentParser(description='Pure Python command-line RSS reader.') parser.add_argument("source", type=str, @@ -123,11 +124,11 @@ def arg_parse(): help="Print result as JSON in stdout") parser.add_argument("--verbose", action="store_true", help="Outputs verbose status messages") - return parser.parse_args() + return parser.parse_args(args) def main(): - args = arg_parse() + args = arg_parse(sys.argv[1:]) if args.version: print('RSS-reader version {}'.format(VERSION)) # program version call return diff --git a/final_task/tests/RSS-unittest.py b/final_task/tests/RSS-unittest.py new file mode 100644 index 0000000..1ebd97b --- /dev/null +++ b/final_task/tests/RSS-unittest.py @@ -0,0 +1,75 @@ +import unittest +import sys +sys.path.append('../') +from rss_reader.rss_reader import RSSReader, arg_parse + +RSS_DICT_TEST = { + 'updated': 'Fri, 08 Nov 2019 13:59:06 GMT', + 'href': 'https://news.yahoo.com/rss', + 'feed': {'title': 'Yahoo News - Latest News & Headlines'}, + 'bozo': 0, + 'status': 200, + 'version': 'rss20', + 'etag': '"c3e4279c4a667ef345dcb64d5344199b"', + 'encoding': 'utf-8', + 'entries': [ + { + 'title': 'Graham now says Trump', + 'published': 'Wed, 06 Nov 2019 14:22:10 -0500', + 'id': 'graham-trump-ukraine-incoherent-quid-pro-quo-192210175.html', + 'summary': '

Trumpday


', + 'link': 'https://news.yahoo.com/graham-trump-ukraine.html' + }, + { + 'title': '2 escaped murder suspects arrested at US-Mexico border', + 'published': 'Thu, 07 Nov 2019 07:25:46 -0500', + 'id': '2-escaped-murder-suspects-arrested-050940220.html', + 'summary': '

borderare


', + 'link': 'https://news.yahoo.com/2-escaped-murder-suspects-arrested.html' + }, + ], +} + + +class RSSTestCase(unittest.TestCase): + def setUp(self): + self.rss = RSSReader('') + + def test_make_news_data(self): + result = self.rss.make_news_data(RSS_DICT_TEST['entries'][0]) + self.assertEqual(result.get('Title'), 'Graham now says Trump') + self.assertEqual(result.get('Date'), 'Wed, 06 Nov 2019 14:22:10 -0500') + self.assertEqual(result.get('Summary'), 'day') + self.assertEqual(result.get('Link'), 'https://news.yahoo.com/graham-trump-ukraine.html') + self.assertEqual(result.get('Image'), 'Trump\nSource of image: test2') + + def test_information_about_site(self): + result = self.rss.information_about_site(RSS_DICT_TEST) + self.assertEqual(result.get('Feed'), 'Yahoo News - Latest News & Headlines') + self.assertEqual(result.get('Updated'), 'Fri, 08 Nov 2019 13:59:06 GMT') + self.assertEqual(result.get('Version'), 'rss20') + + def test_news_data_collection(self): + result = self.rss.news_data_collection(RSS_DICT_TEST) + self.assertEqual(result[0].get('Title'), 'Graham now says Trump') + self.assertEqual(result[0].get('Date'), 'Wed, 06 Nov 2019 14:22:10 -0500') + self.assertEqual(result[0].get('Summary'), 'day') + self.assertEqual(result[0].get('Link'), 'https://news.yahoo.com/graham-trump-ukraine.html') + self.assertEqual(result[0].get('Image'), 'Trump\nSource of image: test2') + self.assertEqual(result[1].get('Title'), '2 escaped murder suspects arrested at US-Mexico border') + self.assertEqual(result[1].get('Date'), 'Thu, 07 Nov 2019 07:25:46 -0500') + self.assertEqual(result[1].get('Summary'), 'are') + self.assertEqual(result[1].get('Link'), 'https://news.yahoo.com/2-escaped-murder-suspects-arrested.html') + self.assertEqual(result[1].get('Image'), 'border\nSource of image: test4') + + def test_arg_parse(self): + parser = arg_parse(['source', '--limit', '1', '--json', '--verbose', '--version']) + self.assertTrue(parser.limit == 1) + self.assertTrue(parser.source == 'source') + self.assertTrue(parser.json) + self.assertTrue(parser.verbose) + self.assertTrue(parser.version) + + +if __name__ == '__main__': + unittest.main() From a57ca3b130bb44097e56c52e86af19fb06a5c2d7 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Wed, 13 Nov 2019 23:04:04 +0300 Subject: [PATCH 03/10] Second Iteration --- __init__.py | 0 final_task/rss_reader/RSSReader.py | 112 +++++++++++++++++++ final_task/rss_reader/RSSReaderException.py | 2 + final_task/rss_reader/__init__.py | 0 final_task/rss_reader/python | 0 final_task/rss_reader/rss_reader.py | 116 +------------------- final_task/setup.py | 16 +++ 7 files changed, 135 insertions(+), 111 deletions(-) create mode 100644 __init__.py create mode 100644 final_task/rss_reader/RSSReader.py create mode 100644 final_task/rss_reader/RSSReaderException.py create mode 100644 final_task/rss_reader/__init__.py create mode 100644 final_task/rss_reader/python diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/final_task/rss_reader/RSSReader.py b/final_task/rss_reader/RSSReader.py new file mode 100644 index 0000000..f99b788 --- /dev/null +++ b/final_task/rss_reader/RSSReader.py @@ -0,0 +1,112 @@ +import datetime +import json +import feedparser +import logging +import sys +from bs4 import BeautifulSoup +from RSSReaderException import RSSReaderException + + +class RSSReader: + + def __init__(self, url, is_verbose=False): + self.url = url + self.is_verbose = is_verbose + self.is_json = False + self.limit = None + logging.basicConfig(format='[%(asctime)s][%(levelname)s]%(message)s', stream=sys.stdout, level=logging.ERROR) + self.logger = logging.getLogger(__name__) + if is_verbose: + self.logger.setLevel(level=logging.INFO) + + def url_parsing(self): + parsed_url = feedparser.parse(self.url) + if parsed_url.status != 200: # URL-access check + self.logger.error("Can't load RSS feed.") + raise RSSReaderException('RSS-reader failed. Status: {}.'.format(parsed_url.status)) + else: + self.logger.info('RSS parsed successfully') + return parsed_url + + def information_about_site(self, parsed_url): + """Function which make dictionary with data of website""" + date = parsed_url.get('updated', datetime.datetime.now()) + return { + 'Feed': parsed_url['feed']['title'], + 'Updated': date, + 'Version': parsed_url['version'], + } + + def make_news_data(self, news): + """ + Function which parsed HTML summary of news and make dictionary with all the necessary news data. + :return dictionary + """ + print(news) + if 'summary' in news: + bs = BeautifulSoup(news['summary'], 'html.parser') + img = bs.find_all('img') + image_data = 'No image' + if img: + image_data = '{}\nSource of image: {}'.format(img[0].get('alt', ""), img[0]['src']) + summary = bs.get_text() + else: + summary = news['title'] + image_data = 'No image' + news_summary = { + 'Title': news['title'], + 'Date': news['published'] if 'published' in news else news['updated'], + 'Link': news['link'], + 'Summary': summary, + 'Image': image_data, + } + return news_summary + + def news_data_collection(self, parsed_url): + """Function that collects data from all news by calling make_news_data. + :return string of dictionaries + """ + all_information_about_news = parsed_url['entries'] + # amount_of_news = len(parsed_url.entries) + all_news = [] + self.logger.info('News gathering') + for news in all_information_about_news[:self.limit]: + try: # Exception when news is failed + dictionary_of_news_data = self.make_news_data(news) + except Exception as ex: + self.logger.error("Error processing news: {} {}".format(type(ex), ex)) + else: + all_news.append(dictionary_of_news_data) # make list of dictionaries of news data for optional output + return all_news + + def parse_to_json(self, dictionary): + return json.dumps(dictionary, indent=4) + + def output(self, about_website, all_news): + """Function which print information about site and a set of news.""" + for key, value in about_website.items(): + print('\n', key, ': ', value) + for number_of_news in all_news: + print("--------------------------------------------------------") + for key, value in number_of_news.items(): + print(key, ': ', value) + + def output_json(self, about_website, all_news): + print(self.parse_to_json([about_website] + all_news)) + + def get_news(self): + """Get news!""" + self.logger.info('Start parsing') + try: + parsed_url = self.url_parsing() + about_website = self.information_about_site(parsed_url) + except Exception as ex: # for any exception after website parsing + self.logger.error("Error reading site data: {}, {}".format(type(ex), ex)) + return + string_of_news_dictionaries = self.news_data_collection(parsed_url) + if self.is_json: + self.logger.info('Convert to JSON-format') + self.output_json(about_website, string_of_news_dictionaries) + else: + self.logger.info('Output news') + self.output(about_website, string_of_news_dictionaries) diff --git a/final_task/rss_reader/RSSReaderException.py b/final_task/rss_reader/RSSReaderException.py new file mode 100644 index 0000000..b3750dd --- /dev/null +++ b/final_task/rss_reader/RSSReaderException.py @@ -0,0 +1,2 @@ +class RSSReaderException(Exception): + pass diff --git a/final_task/rss_reader/__init__.py b/final_task/rss_reader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/final_task/rss_reader/python b/final_task/rss_reader/python new file mode 100644 index 0000000..e69de29 diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index a2b0053..99a9b03 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -1,114 +1,9 @@ -import feedparser import sys -import datetime import argparse -import json -from bs4 import BeautifulSoup +from RSSReader import RSSReader -DEFAULT_LIMIT_COUNT = 3 -VERSION = '1.0' - -class RSSReader: - is_verbose = False # logging is disabled by default - is_json = False - limit = DEFAULT_LIMIT_COUNT - - def __init__(self, url): - self.url = url - - def url_parsing(self): - parsed_url = feedparser.parse(self.url) - if parsed_url.status != 200: # URL-access check - print("Can't load RSS feed.") - raise Exception('RSS-reader failed. Status: {}.'.format(parsed_url.status)) - else: - self.log('RSS parsed successfully') - return parsed_url - - def information_about_site(self, parsed_url): - """Function which make dictionary with data of website""" - return { - 'Feed': parsed_url['feed']['title'], - 'Updated': parsed_url['updated'], - 'Version': parsed_url['version'], - } - - def make_news_data(self, news): - """ - Function which parsed HTML summary of news and make dictionary with all the necessary news data. - :return dictionary - """ - bs = BeautifulSoup(news['summary'], 'html.parser') - img = bs.find_all('img') - image_data = 'No image' - if len(img) > 0: - image_data = '{}\nSource of image: {}'.format(img[0]['alt'], img[0]['src']) - news_summary = { - 'Title': news['title'], - 'Date': news['published'], - 'Summary': bs.get_text(), - 'Link': news['link'], - 'Image': image_data, - } - return news_summary - - def news_data_collection(self, parsed_url): - """Function that collects data from all news by calling make_news_data. - :return string of dictionaries - """ - all_information_about_news = parsed_url['entries'] - # amount_of_news = len(parsed_url.entries) - all_news = [] - self.log('News gathering') - for news in all_information_about_news[:self.limit]: - try: # Exception when news is failed - dictionary_of_news_data = self.make_news_data(news) - except Exception as ex: - self.log("Error processing news: {} {}".format(type(ex), ex)) - continue # continues to process the following news - else: - all_news.append(dictionary_of_news_data) # make list of dictionaries of news data for optional output - return all_news - - def log(self, message): - """Print time and log message if verbose is active.""" - if self.is_verbose: - print(datetime.datetime.now(), message) - - def parse_to_json(self, dictionary): - return json.dumps(dictionary, indent=4) - - def output(self, about_website, all_news): - """Function which print information about site and a set of news.""" - print() - for key, value in about_website.items(): - print(key, ': ', value) - print() - for number_of_news in all_news: - print() - for key, value in number_of_news.items(): - print(key, ': ', value) - - def output_json(self, about_website, all_news): - print(self.parse_to_json([about_website] + all_news)) - - def doing_everything_in_class(self): - """Get news""" - self.log('Start parsing') - try: - parsed_url = self.url_parsing() - about_website = self.information_about_site(parsed_url) - except Exception as ex: # for any exception after website parsing - self.log("Error reading site data: {}, {}".format(type(ex), ex)) - return - string_of_news_dictionaries = self.news_data_collection(parsed_url) - if self.is_json: - self.log('Convert to JSON-format') - self.output_json(about_website, string_of_news_dictionaries) - else: - self.log('Output news') - self.output(about_website, string_of_news_dictionaries) +VERSION = '2.0' def arg_parse(args): @@ -116,7 +11,7 @@ def arg_parse(args): parser = argparse.ArgumentParser(description='Pure Python command-line RSS reader.') parser.add_argument("source", type=str, help="RSS URL") - parser.add_argument("--limit", type=int, default=DEFAULT_LIMIT_COUNT, + parser.add_argument("--limit", type=int, help="Limit news topics if this parameter provided") parser.add_argument("--version", action="store_true", help="Print version info") @@ -132,11 +27,10 @@ def main(): if args.version: print('RSS-reader version {}'.format(VERSION)) # program version call return - reader = RSSReader(args.source) + reader = RSSReader(args.source, args.verbose) reader.limit = args.limit - reader.is_verbose = args.verbose reader.is_json = args.json - reader.doing_everything_in_class() + reader.get_news() if __name__ == '__main__': diff --git a/final_task/setup.py b/final_task/setup.py index e69de29..29a9c2e 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup, find_packages +from rss_reader/rss_reader import VERSION + +setup( + name='rss_reader', + version=VERSION, + packages=find_packages(), + install_requires=['feedparser', 'bs4'], + author="Victoria Kondrat'eva", + author_email='sam.kondrateva@gmail.com', + url='https://github.com/Victoria-Sam', + description='RSS Reader', + entry_points={'console_scripts': ['Rss-Reader = rss_reader.rss_reader:main']}, + keywords="rss reader", + python_requires = '>=3.7', +) \ No newline at end of file From a64631879ed73caa09e25c97221901c0a7cf648b Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Mon, 18 Nov 2019 00:19:05 +0300 Subject: [PATCH 04/10] Third iteration in process... --- final_task/rss_reader/NewsCache.py | 42 +++++++++++++++++++ final_task/rss_reader/RSSReader.py | 64 ++++++++++++++++++----------- final_task/rss_reader/rss_reader.py | 11 ++++- 3 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 final_task/rss_reader/NewsCache.py diff --git a/final_task/rss_reader/NewsCache.py b/final_task/rss_reader/NewsCache.py new file mode 100644 index 0000000..b31630c --- /dev/null +++ b/final_task/rss_reader/NewsCache.py @@ -0,0 +1,42 @@ +import json +import os.path +import logging + + +class NewsCache: + + def __init__(self, file_name): + self.logger = logging.getLogger(__name__) + self.file_name = file_name + self.logger.info("Check for cache file...") + if os.path.isfile(file_name): + try: + with open(file_name, "r") as json_file: + self.cache_news = json.load(json_file) + self.logger.info("Reading cache file...") + except Exception as ex: + self.logger.error("Error reading file: {} {}".format(type(ex), ex)) + self.cache_news = {} + else: + self.logger.warning("Warning! Caching file is not found!") + self.cache_news = {} + + def caching(self, all_news, url): + for dictionary in all_news: + date_in_news = dictionary.pop('Date key') + title = dictionary['Title'] + self.cache_news.setdefault(date_in_news, {}).setdefault(url, {}).setdefault(title, dictionary) + with open(self.file_name, "w") as file_to_write: + self.logger.info("News caching...") + file_to_write.write(json.dumps(self.cache_news)) + + def returning(self, desired_date, desired_url=None): + self.logger.info('News search in cache...') + caching_news_list = [] + if desired_url is not None: + caching_news_list = list(self.cache_news.get(desired_date, {}).get(desired_url, {}).values()) + else: + answer = self.cache_news.get(desired_date, {}) + for title in answer.values(): + caching_news_list += title.values() + return caching_news_list diff --git a/final_task/rss_reader/RSSReader.py b/final_task/rss_reader/RSSReader.py index f99b788..fff34b1 100644 --- a/final_task/rss_reader/RSSReader.py +++ b/final_task/rss_reader/RSSReader.py @@ -2,22 +2,22 @@ import json import feedparser import logging -import sys from bs4 import BeautifulSoup from RSSReaderException import RSSReaderException +from NewsCache import NewsCache + +CACHE_FILE_NAME = "Cache_file.json" class RSSReader: - def __init__(self, url, is_verbose=False): - self.url = url - self.is_verbose = is_verbose + def __init__(self): + self.cache = NewsCache(CACHE_FILE_NAME) + self.date = None + self.url = None self.is_json = False self.limit = None - logging.basicConfig(format='[%(asctime)s][%(levelname)s]%(message)s', stream=sys.stdout, level=logging.ERROR) self.logger = logging.getLogger(__name__) - if is_verbose: - self.logger.setLevel(level=logging.INFO) def url_parsing(self): parsed_url = feedparser.parse(self.url) @@ -25,7 +25,7 @@ def url_parsing(self): self.logger.error("Can't load RSS feed.") raise RSSReaderException('RSS-reader failed. Status: {}.'.format(parsed_url.status)) else: - self.logger.info('RSS parsed successfully') + self.logger.info('RSS parsed successfully!') return parsed_url def information_about_site(self, parsed_url): @@ -42,7 +42,6 @@ def make_news_data(self, news): Function which parsed HTML summary of news and make dictionary with all the necessary news data. :return dictionary """ - print(news) if 'summary' in news: bs = BeautifulSoup(news['summary'], 'html.parser') img = bs.find_all('img') @@ -53,12 +52,15 @@ def make_news_data(self, news): else: summary = news['title'] image_data = 'No image' + date_info = news['published_parsed'] if 'published' in news else news['updated_parsed'] + date_key = str("{}{}{}".format(date_info.tm_year, date_info.tm_mon, date_info.tm_mday)) news_summary = { 'Title': news['title'], 'Date': news['published'] if 'published' in news else news['updated'], 'Link': news['link'], 'Summary': summary, 'Image': image_data, + 'Date key': date_key } return news_summary @@ -82,31 +84,47 @@ def news_data_collection(self, parsed_url): def parse_to_json(self, dictionary): return json.dumps(dictionary, indent=4) - def output(self, about_website, all_news): + def output(self, all_news, about_website=None): """Function which print information about site and a set of news.""" - for key, value in about_website.items(): - print('\n', key, ': ', value) + if about_website is not None: + for key, value in about_website.items(): + print('\n', key, ': ', value) for number_of_news in all_news: print("--------------------------------------------------------") for key, value in number_of_news.items(): print(key, ': ', value) - def output_json(self, about_website, all_news): - print(self.parse_to_json([about_website] + all_news)) + def output_json(self, all_news, about_website=None): + if about_website is not None: + print(self.parse_to_json([about_website] + all_news)) + else: + print(self.parse_to_json(all_news)) def get_news(self): """Get news!""" - self.logger.info('Start parsing') - try: - parsed_url = self.url_parsing() - about_website = self.information_about_site(parsed_url) - except Exception as ex: # for any exception after website parsing - self.logger.error("Error reading site data: {}, {}".format(type(ex), ex)) + if self.date is None: + if self.url is None: + self.logger.error("URL is not provided.") + return + self.logger.info('Start parsing...') + try: + parsed_url = self.url_parsing() + about_website = self.information_about_site(parsed_url) + except Exception as ex: # for any exception after website parsing + self.logger.error("Error reading site data: {}, {}.".format(type(ex), ex)) + return + string_of_news_dictionaries = self.news_data_collection(parsed_url) + self.cache.caching(string_of_news_dictionaries, self.url) + else: + string_of_news_dictionaries = self.cache.returning(self.date, self.url)[:self.limit] + about_website = None + # empty list and return + if not string_of_news_dictionaries: + self.logger.error('There is no news.') return - string_of_news_dictionaries = self.news_data_collection(parsed_url) if self.is_json: self.logger.info('Convert to JSON-format') - self.output_json(about_website, string_of_news_dictionaries) + self.output_json(string_of_news_dictionaries, about_website) else: self.logger.info('Output news') - self.output(about_website, string_of_news_dictionaries) + self.output(string_of_news_dictionaries, about_website) diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index 99a9b03..9fa9d62 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -1,5 +1,6 @@ import sys import argparse +import logging from RSSReader import RSSReader @@ -9,7 +10,7 @@ def arg_parse(args): """Function which parsed command-line arguments.""" parser = argparse.ArgumentParser(description='Pure Python command-line RSS reader.') - parser.add_argument("source", type=str, + parser.add_argument("--source", type=str, help="RSS URL") parser.add_argument("--limit", type=int, help="Limit news topics if this parameter provided") @@ -19,15 +20,21 @@ def arg_parse(args): help="Print result as JSON in stdout") parser.add_argument("--verbose", action="store_true", help="Outputs verbose status messages") + parser.add_argument("--date", type=str, + help="Return news from cache with that date.") return parser.parse_args(args) def main(): args = arg_parse(sys.argv[1:]) + level = logging.INFO if args.verbose else logging.ERROR + logging.basicConfig(format='[%(asctime)s][%(levelname)s]%(message)s', stream=sys.stdout, level=level) if args.version: print('RSS-reader version {}'.format(VERSION)) # program version call return - reader = RSSReader(args.source, args.verbose) + reader = RSSReader() + reader.date = args.date + reader.url = args.source reader.limit = args.limit reader.is_json = args.json reader.get_news() From b0b343eaddf69d3cb9dd24e049fa93c8ff32b4c0 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Tue, 19 Nov 2019 00:51:35 +0300 Subject: [PATCH 05/10] Tests to third iteration. --- .gitignore | 4 +- final_task/tests/NewsCache_test.py | 49 +++++++++++++++++++ .../tests/{RSS-unittest.py => RSS_test.py} | 14 ++++-- 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 final_task/tests/NewsCache_test.py rename final_task/tests/{RSS-unittest.py => RSS_test.py} (87%) diff --git a/.gitignore b/.gitignore index 88315da..540a77b 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,6 @@ coverage.xml *.pot # Sphinx documentation -docs/_build/ \ No newline at end of file +docs/_build/ + +Test_file.json \ No newline at end of file diff --git a/final_task/tests/NewsCache_test.py b/final_task/tests/NewsCache_test.py new file mode 100644 index 0000000..f728195 --- /dev/null +++ b/final_task/tests/NewsCache_test.py @@ -0,0 +1,49 @@ +import unittest +import json +import sys +import copy +sys.path.append('../rss_reader') +from NewsCache import NewsCache + +TEST_NEWS_LIST = [ + {"Title": "Air racing tournament unveils an all-electric sports aircraft", + "Date": "Sun, 17 Nov 2019 15:35:00 -0500", + "Link": "link", + "Summary": "Test text", + "Image": "\nSource of image: img_link", + "Date key": "20191117"}] + +FILE_CONTENTS = {"20191117": + {"https://www.engadget.com/rss.xml": + {"Air racing tournament unveils an all-electric sports aircraft": + {"Title": "Air racing tournament unveils an all-electric sports aircraft", + "Date": "Sun, 17 Nov 2019 15:35:00 -0500", + "Link": "link", + "Summary": "Test text", + "Image": "\nSource of image: img_link"}}}} + + +class MyTestCase(unittest.TestCase): + + def setUp(self): + self.cache = NewsCache('Test_file.json') + test_news = copy.deepcopy(TEST_NEWS_LIST) + self.cache.caching(test_news, 'https://www.engadget.com/rss.xml') + + def test_caching(self): + with open('Test_file.json', "r") as test_json: + content = test_json.read() + self.assertEqual(json.loads(content), FILE_CONTENTS) + self.assertEqual(json.loads(content).get('20191117'), FILE_CONTENTS['20191117']) + + def test_returning(self): + result = self.cache.returning('20191117', 'https://www.engadget.com/rss.xml') + self.assertEqual(result[0].get('Title'), "Air racing tournament unveils an all-electric sports aircraft") + self.assertEqual(result[0].get('Date'), "Sun, 17 Nov 2019 15:35:00 -0500") + self.assertEqual(result[0].get('Link'), "link") + self.assertEqual(result[0].get('Summary'), "Test text") + self.assertEqual(result[0].get('Image'), "\nSource of image: img_link") + + +if __name__ == '__main__': + unittest.main() diff --git a/final_task/tests/RSS-unittest.py b/final_task/tests/RSS_test.py similarity index 87% rename from final_task/tests/RSS-unittest.py rename to final_task/tests/RSS_test.py index 1ebd97b..962e099 100644 --- a/final_task/tests/RSS-unittest.py +++ b/final_task/tests/RSS_test.py @@ -1,7 +1,9 @@ import unittest import sys -sys.path.append('../') -from rss_reader.rss_reader import RSSReader, arg_parse +import time +sys.path.append('../rss_reader') +from rss_reader import arg_parse +from RSSReader import RSSReader RSS_DICT_TEST = { 'updated': 'Fri, 08 Nov 2019 13:59:06 GMT', @@ -16,6 +18,7 @@ { 'title': 'Graham now says Trump', 'published': 'Wed, 06 Nov 2019 14:22:10 -0500', + 'published_parsed': time.strptime('06 Nov 2019', '%d %b %Y'), 'id': 'graham-trump-ukraine-incoherent-quid-pro-quo-192210175.html', 'summary': '

Trumpday


', 'link': 'https://news.yahoo.com/graham-trump-ukraine.html' @@ -23,6 +26,7 @@ { 'title': '2 escaped murder suspects arrested at US-Mexico border', 'published': 'Thu, 07 Nov 2019 07:25:46 -0500', + 'published_parsed': time.strptime('07 Nov 2019', '%d %b %Y'), 'id': '2-escaped-murder-suspects-arrested-050940220.html', 'summary': '

borderare


', 'link': 'https://news.yahoo.com/2-escaped-murder-suspects-arrested.html' @@ -33,7 +37,7 @@ class RSSTestCase(unittest.TestCase): def setUp(self): - self.rss = RSSReader('') + self.rss = RSSReader() def test_make_news_data(self): result = self.rss.make_news_data(RSS_DICT_TEST['entries'][0]) @@ -63,12 +67,14 @@ def test_news_data_collection(self): self.assertEqual(result[1].get('Image'), 'border\nSource of image: test4') def test_arg_parse(self): - parser = arg_parse(['source', '--limit', '1', '--json', '--verbose', '--version']) + parser = arg_parse(['--source', 'source', '--limit', '1', '--json', '--verbose', '--version', + '--date', '20191117']) self.assertTrue(parser.limit == 1) self.assertTrue(parser.source == 'source') self.assertTrue(parser.json) self.assertTrue(parser.verbose) self.assertTrue(parser.version) + self.assertTrue(parser.date == '20191117') if __name__ == '__main__': From 59f8bcb8a8f13188e96602f6584e3d60c5987554 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Mon, 25 Nov 2019 00:44:10 +0300 Subject: [PATCH 06/10] Fourth iteration --- final_task/README.md | 87 ++++++++++++++++--- final_task/rss_reader/RSSReader.py | 68 +++++++-------- final_task/rss_reader/output.py | 116 +++++++++++++++++++++++++ final_task/rss_reader/requirements.txt | 3 + final_task/rss_reader/rss_reader.py | 14 +-- final_task/setup.py | 4 +- final_task/tests/NewsCache_test.py | 6 +- final_task/tests/RSS_test.py | 20 +++-- 8 files changed, 248 insertions(+), 70 deletions(-) create mode 100644 final_task/rss_reader/output.py diff --git a/final_task/README.md b/final_task/README.md index 1d6908c..cff4142 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -1,19 +1,74 @@ #The best RSS-reader! Program is named rss_reader.py. +##Instalation +The recommended way to install rss-reader is with pip: +* pip install rss_reader - for Windows +* sudo pip install rss_reader - for Linux and Mac -To run the program from the command line, you must write the file name and string with URL-address of news site. +##Description and Functions +``` +usage: rss_reader.py [-h] [--limit LIMIT] [--version] [--json] [--verbose] + [--date DATE] [--to-pdf] [--to-] + [source] + +Pure Python command-line RSS reader. + +positional arguments: + source RSS URL + +optional arguments: + -h, --help show this help message and exit + --limit LIMIT Limit news topics if this parameter provided + --version Print version info + --json Print result as JSON in stdout + --verbose Outputs verbose status messages + --date DATE Return news from cache with that date. + --to-pdf Conversion of news in the pdf format. + --to- Conversion of news in the ___ format. +``` -Also it have such parameters like: -* --version (Print version info); -* --json (Print result as JSON in stdout); -* --verbose (Outputs verbose status messages); -* --limit LIMIT (Limit news topics if this parameter provided, type(LIMIT)=int). + +To run the program from the command line, you must write the file name and string with URL-address of news site. It's print a brief description of the news information that appeared on the news site in human-readable -format. +format and save them in cache. -JSON format example: +Example: +```python rss_reader.py "https://news.yahoo.com/rss/" --limit 1 + +Feed: Yahoo News - Latest News & Headlines + +Updated: Sun, 24 Nov 2019 21:19:04 GMT +Version: rss20 +-------------------------------------------------------- +Title: Ukraine's President Zelensky said he didn't feel pressured by Trump. Here +'s why that's bogus. +Date: Sat, 23 Nov 2019 08:57:00 -0500 +Link: https://news.yahoo.com/ukraines-president-zelensky-said-didnt-135700678.ht +ml +Summary: [Image: Ukraine's President Zelensky said he didn't feel pressured by +Trump. Here's why that's bogus.] The US government's assistance to Ukraine is vi +tal as it contends with an ongoing conflict with pro-Russian separatists in the +Donbas region. +Source of image: http://l2.yimg.com/uu/api/res/1.2/3h5JxcDjAP1pHR6KUD2JMQ--/YXBw +aWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en-US/business_insider_ +articles_888/7a21ff2ee09c3b4285094c4e64e9602c +``` + +###How to read saving news: +If you want to read saved news of a certain date, you must enter a --date in %Y%m%d format and the program will work +without any connecting to the Internet. + +It is also possible to select news from a specific source of a certain date, for this you must enter the url-address +and --date that you want. + +###What formats can you convert to? How to do it. +Program can convert news in ```--to_pdf``` and ```--to_epub``` format in certain files. +The name of the file generates automatically like 'RSS Reader.pdf/epub'. Also program convert news in ```--json``` format +but print result as JSON in stdout. + +JSON format example: ```json [ { @@ -30,6 +85,18 @@ JSON format example: } ] ``` +#####Ways for converting: +- *You can convert from cache* + +For this case news conversion is not depend on internet. +With ```rss_reader.py --date 20191117 --limit 1 --to_pdf``` one news for the specified day +would be converted (the same with ```--to_epub```) and would be generated or overwritten file 'RSS reader.pdf'. + +- *You can convert news from the Internet* + +For this case Internet is required. With ```rss_reader.py https://news.yahoo.com/rss/ --limit 1 --to_pdf``` one news +from listed link would be converted (the same with ```--to_epub```)and would be generated or overwritten +file 'RSS reader.pdf' + + -The following external libraries are used in this program: -feedparser and bs4. diff --git a/final_task/rss_reader/RSSReader.py b/final_task/rss_reader/RSSReader.py index fff34b1..7c33473 100644 --- a/final_task/rss_reader/RSSReader.py +++ b/final_task/rss_reader/RSSReader.py @@ -1,22 +1,25 @@ import datetime -import json import feedparser import logging from bs4 import BeautifulSoup from RSSReaderException import RSSReaderException from NewsCache import NewsCache +from unidecode import unidecode +from output import get_output_function -CACHE_FILE_NAME = "Cache_file.json" +CACHE_FILE_NAME = "Cache file.json" class RSSReader: - def __init__(self): + def __init__(self, date, url, limit, is_json, is_pdf, is_epub): self.cache = NewsCache(CACHE_FILE_NAME) - self.date = None - self.url = None - self.is_json = False - self.limit = None + self.date = date + self.url = url + self.is_json = is_json + self.limit = limit + self.is_pdf = is_pdf + self.is_epub = is_epub self.logger = logging.getLogger(__name__) def url_parsing(self): @@ -30,11 +33,11 @@ def url_parsing(self): def information_about_site(self, parsed_url): """Function which make dictionary with data of website""" - date = parsed_url.get('updated', datetime.datetime.now()) + date = parsed_url.get('updated', str(datetime.datetime.now())) return { - 'Feed': parsed_url['feed']['title'], + 'Feed': unidecode(parsed_url['feed']['title']), 'Updated': date, - 'Version': parsed_url['version'], + 'Version': unidecode(parsed_url['version']), } def make_news_data(self, news): @@ -42,24 +45,26 @@ def make_news_data(self, news): Function which parsed HTML summary of news and make dictionary with all the necessary news data. :return dictionary """ + image_link = 'No image' if 'summary' in news: + # convert_to_epub(news['summary']) bs = BeautifulSoup(news['summary'], 'html.parser') img = bs.find_all('img') - image_data = 'No image' if img: - image_data = '{}\nSource of image: {}'.format(img[0].get('alt', ""), img[0]['src']) - summary = bs.get_text() + image_description = unidecode(img[0].get('alt', 'no description')) + image_link = img[0].get('src') + img[0].replaceWith(f' [Image: {image_description}] ') + summary = unidecode(str(bs.text)) else: summary = news['title'] - image_data = 'No image' date_info = news['published_parsed'] if 'published' in news else news['updated_parsed'] date_key = str("{}{}{}".format(date_info.tm_year, date_info.tm_mon, date_info.tm_mday)) news_summary = { - 'Title': news['title'], + 'Title': unidecode(news['title']), 'Date': news['published'] if 'published' in news else news['updated'], 'Link': news['link'], 'Summary': summary, - 'Image': image_data, + 'Source of image': image_link, 'Date key': date_key } return news_summary @@ -81,25 +86,6 @@ def news_data_collection(self, parsed_url): all_news.append(dictionary_of_news_data) # make list of dictionaries of news data for optional output return all_news - def parse_to_json(self, dictionary): - return json.dumps(dictionary, indent=4) - - def output(self, all_news, about_website=None): - """Function which print information about site and a set of news.""" - if about_website is not None: - for key, value in about_website.items(): - print('\n', key, ': ', value) - for number_of_news in all_news: - print("--------------------------------------------------------") - for key, value in number_of_news.items(): - print(key, ': ', value) - - def output_json(self, all_news, about_website=None): - if about_website is not None: - print(self.parse_to_json([about_website] + all_news)) - else: - print(self.parse_to_json(all_news)) - def get_news(self): """Get news!""" if self.date is None: @@ -118,13 +104,17 @@ def get_news(self): else: string_of_news_dictionaries = self.cache.returning(self.date, self.url)[:self.limit] about_website = None - # empty list and return if not string_of_news_dictionaries: self.logger.error('There is no news.') return if self.is_json: - self.logger.info('Convert to JSON-format') - self.output_json(string_of_news_dictionaries, about_website) + converter = 'json' + elif self.is_pdf: + converter = 'pdf' + elif self.is_epub: + converter = 'epub' else: self.logger.info('Output news') - self.output(string_of_news_dictionaries, about_website) + converter = 'text' + output = get_output_function(converter) + output(self.logger, string_of_news_dictionaries, about_website) diff --git a/final_task/rss_reader/output.py b/final_task/rss_reader/output.py new file mode 100644 index 0000000..4a4e134 --- /dev/null +++ b/final_task/rss_reader/output.py @@ -0,0 +1,116 @@ +import os +import json +import urllib +from ebooklib import epub +from fpdf import FPDF + + +def get_output_function(converter): + if converter == 'json': + return output_json + elif converter == 'pdf': + return output_pdf + elif converter == 'epub': + return output_epub + elif converter == 'text': + return output + + +def output(logger, all_news, about_website=None, ): + """Function which print information about site and a set of news.""" + logger.info('Output news') + if about_website is not None: + for key, value in about_website.items(): + print(f'\n{key}: {value}') + for number_of_news in all_news: + print("--------------------------------------------------------") + for key, value in number_of_news.items(): + print(f'{key}: {value}') + + +def parse_to_json(dictionary): + return json.dumps(dictionary, indent=4) + + +def output_json(logger, all_news, about_website=None): + if about_website is not None: + logger.info('Convert to JSON-format') + print(parse_to_json([about_website] + all_news)) + else: + logger.info('Convert news to JSON-format fom cache') + print(parse_to_json(all_news)) + + +def output_pdf(logger, all_news, about_website=None): + logger.info('Convert to PDF-format') + pdf = FPDF() + pdf.add_page() + if about_website is not None: + pdf.set_font("Arial", "B", size=14) + pdf.set_fill_color(200, 220, 255) + for value in about_website.values(): + line = 1 + pdf.cell(190, 8, txt=value, ln=line, align="C") + line += 1 + pdf.set_font("Arial", size=10) + pdf.set_line_width(1) + pdf.set_draw_color(35, 41, 153) + for news in all_news: + link = news['Source of image'] + for key, value in news.items(): + if key != 'Summary': + pdf.multi_cell(190, 6, txt=f'{key}: {value}', align="L") + else: + position_y = pdf.get_y() + try: + filename, _ = urllib.request.urlretrieve(link) + pdf.image(filename, 80, position_y, h=30, type='jpeg', link=link) + pdf.ln(31) + os.remove(filename) + except Exception as ex: + logger.error("Error finding image: {}, {}.".format(type(ex), ex)) + pdf.multi_cell(190, 6, txt=f'{key}: {value}', align="L") + position_y = pdf.get_y() + pdf.set_line_width(1) + pdf.set_draw_color(35, 41, 153) + pdf.line(10, position_y, 200, position_y) + logger.info('Creating of PDF-file') + try: + pdf.output("RSS news.pdf") + logger.info('Converted successfully!') + except Exception as ex: + logger.error("Error finding image: {}, {}.".format(type(ex), ex)) + + +def output_epub(logger, all_news, about_website=None): + logger.info('Convert to EPUB-format') + book = epub.EpubBook() + if about_website is not None: + book.set_title(f'RSS news from {about_website["Feed"]}') + else: + book.set_title('RSS news') + book.set_language('en') + chapters = [] + for news in all_news: + chapter = epub.EpubHtml(title=f'Chapter of {news["Date"]}', file_name=f'chap {news["Title"]}.xhtml') + title = f'

{news["Title"]}

' + if news["Source of image"] == "No image": + image = '' + else: + image = f'' + string_of_epub_content = f'Date: {news["Date"]}
' + string_of_epub_content += f'Link: {news["Link"]}
' + string_of_epub_content += f'Summary: {news["Summary"]}
' + string_of_epub_content += f'Source of image: {news["Source of image"]}
' + chapter.content = f'{title}{image}

{string_of_epub_content}

' + book.add_item(chapter) + chapters.append(chapter) + book.add_item(epub.EpubNcx()) + book.add_item(epub.EpubNav()) + book.spine = chapters + logger.info('Creating of PDF-file') + try: + epub.write_epub('RSS news.epub', book, {}) + logger.info('Converted successfully!') + except Exception as ex: + logger.error("Error finding image: {}, {}.".format(type(ex), ex)) diff --git a/final_task/rss_reader/requirements.txt b/final_task/rss_reader/requirements.txt index f57478b..6d32dcb 100644 --- a/final_task/rss_reader/requirements.txt +++ b/final_task/rss_reader/requirements.txt @@ -1,2 +1,5 @@ bs4 feedparser +unidecode +fpdf +ebooklib \ No newline at end of file diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index 9fa9d62..83a7e82 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -4,13 +4,13 @@ from RSSReader import RSSReader -VERSION = '2.0' +VERSION = '4.0' def arg_parse(args): """Function which parsed command-line arguments.""" parser = argparse.ArgumentParser(description='Pure Python command-line RSS reader.') - parser.add_argument("--source", type=str, + parser.add_argument("source", type=str, nargs='?', help="RSS URL") parser.add_argument("--limit", type=int, help="Limit news topics if this parameter provided") @@ -22,6 +22,10 @@ def arg_parse(args): help="Outputs verbose status messages") parser.add_argument("--date", type=str, help="Return news from cache with that date.") + parser.add_argument("--to_pdf", action="store_true", + help="Conversion of news in the pdf format.") + parser.add_argument("--to_epub", action="store_true", + help="Conversion of news in the ___ format.") return parser.parse_args(args) @@ -32,11 +36,7 @@ def main(): if args.version: print('RSS-reader version {}'.format(VERSION)) # program version call return - reader = RSSReader() - reader.date = args.date - reader.url = args.source - reader.limit = args.limit - reader.is_json = args.json + reader = RSSReader(args.date, args.source, args.limit, args.json, args.to_pdf, args.to_epub) reader.get_news() diff --git a/final_task/setup.py b/final_task/setup.py index 29a9c2e..01540b7 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -5,12 +5,12 @@ name='rss_reader', version=VERSION, packages=find_packages(), - install_requires=['feedparser', 'bs4'], + install_requires=['feedparser', 'bs4', 'unidecode', 'fpdf', 'ebooklib'], author="Victoria Kondrat'eva", author_email='sam.kondrateva@gmail.com', url='https://github.com/Victoria-Sam', description='RSS Reader', entry_points={'console_scripts': ['Rss-Reader = rss_reader.rss_reader:main']}, - keywords="rss reader", + keywords="rss-reader", python_requires = '>=3.7', ) \ No newline at end of file diff --git a/final_task/tests/NewsCache_test.py b/final_task/tests/NewsCache_test.py index f728195..e666456 100644 --- a/final_task/tests/NewsCache_test.py +++ b/final_task/tests/NewsCache_test.py @@ -10,7 +10,7 @@ "Date": "Sun, 17 Nov 2019 15:35:00 -0500", "Link": "link", "Summary": "Test text", - "Image": "\nSource of image: img_link", + "Source of image": "img_link", "Date key": "20191117"}] FILE_CONTENTS = {"20191117": @@ -20,7 +20,7 @@ "Date": "Sun, 17 Nov 2019 15:35:00 -0500", "Link": "link", "Summary": "Test text", - "Image": "\nSource of image: img_link"}}}} + "Source of image": "img_link"}}}} class MyTestCase(unittest.TestCase): @@ -42,7 +42,7 @@ def test_returning(self): self.assertEqual(result[0].get('Date'), "Sun, 17 Nov 2019 15:35:00 -0500") self.assertEqual(result[0].get('Link'), "link") self.assertEqual(result[0].get('Summary'), "Test text") - self.assertEqual(result[0].get('Image'), "\nSource of image: img_link") + self.assertEqual(result[0].get('Source of image'), "img_link") if __name__ == '__main__': diff --git a/final_task/tests/RSS_test.py b/final_task/tests/RSS_test.py index 962e099..5f50fee 100644 --- a/final_task/tests/RSS_test.py +++ b/final_task/tests/RSS_test.py @@ -37,15 +37,15 @@ class RSSTestCase(unittest.TestCase): def setUp(self): - self.rss = RSSReader() + self.rss = RSSReader(None, None, None, None, None, None) def test_make_news_data(self): result = self.rss.make_news_data(RSS_DICT_TEST['entries'][0]) self.assertEqual(result.get('Title'), 'Graham now says Trump') self.assertEqual(result.get('Date'), 'Wed, 06 Nov 2019 14:22:10 -0500') - self.assertEqual(result.get('Summary'), 'day') + self.assertEqual(result.get('Summary'), ' [Image: Trump] day') self.assertEqual(result.get('Link'), 'https://news.yahoo.com/graham-trump-ukraine.html') - self.assertEqual(result.get('Image'), 'Trump\nSource of image: test2') + self.assertEqual(result.get('Source of image'), 'test2') def test_information_about_site(self): result = self.rss.information_about_site(RSS_DICT_TEST) @@ -57,24 +57,26 @@ def test_news_data_collection(self): result = self.rss.news_data_collection(RSS_DICT_TEST) self.assertEqual(result[0].get('Title'), 'Graham now says Trump') self.assertEqual(result[0].get('Date'), 'Wed, 06 Nov 2019 14:22:10 -0500') - self.assertEqual(result[0].get('Summary'), 'day') + self.assertEqual(result[0].get('Summary'), ' [Image: Trump] day') self.assertEqual(result[0].get('Link'), 'https://news.yahoo.com/graham-trump-ukraine.html') - self.assertEqual(result[0].get('Image'), 'Trump\nSource of image: test2') + self.assertEqual(result[0].get('Source of image'), 'test2') self.assertEqual(result[1].get('Title'), '2 escaped murder suspects arrested at US-Mexico border') self.assertEqual(result[1].get('Date'), 'Thu, 07 Nov 2019 07:25:46 -0500') - self.assertEqual(result[1].get('Summary'), 'are') + self.assertEqual(result[1].get('Summary'), ' [Image: border] are') self.assertEqual(result[1].get('Link'), 'https://news.yahoo.com/2-escaped-murder-suspects-arrested.html') - self.assertEqual(result[1].get('Image'), 'border\nSource of image: test4') + self.assertEqual(result[1].get('Source of image'), 'test4') def test_arg_parse(self): - parser = arg_parse(['--source', 'source', '--limit', '1', '--json', '--verbose', '--version', - '--date', '20191117']) + parser = arg_parse(['source', '--limit', '1', '--json', '--verbose', '--version', + '--date', '20191117', '--to_pdf', '--to_epub']) self.assertTrue(parser.limit == 1) self.assertTrue(parser.source == 'source') self.assertTrue(parser.json) self.assertTrue(parser.verbose) self.assertTrue(parser.version) self.assertTrue(parser.date == '20191117') + self.assertTrue(parser.to_pdf) + self.assertTrue(parser.to_epub) if __name__ == '__main__': From 035a4fc13e2cbe704ad8f9e3de985757b328c206 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Mon, 25 Nov 2019 20:50:13 +0300 Subject: [PATCH 07/10] Fourth iteration fixed --- final_task/README.md | 37 +++++++++---------- final_task/rss_reader/NewsCache.py | 7 ++++ final_task/rss_reader/RSSReader.py | 15 ++++---- final_task/rss_reader/output.py | 20 ++++++----- final_task/rss_reader/rss_reader.py | 4 +-- final_task/tests/NewsCache_test.py | 2 +- final_task/tests/RSS_test.py | 6 ++-- final_task/tests/output_test.py | 55 +++++++++++++++++++++++++++++ 8 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 final_task/tests/output_test.py diff --git a/final_task/README.md b/final_task/README.md index cff4142..fcd7d22 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -8,26 +8,25 @@ The recommended way to install rss-reader is with pip: ##Description and Functions ``` usage: rss_reader.py [-h] [--limit LIMIT] [--version] [--json] [--verbose] - [--date DATE] [--to-pdf] [--to-] + [--date DATE] [--to_pdf TO_PDF] [--to_epub TO_EPUB] [source] Pure Python command-line RSS reader. positional arguments: - source RSS URL + source RSS URL optional arguments: - -h, --help show this help message and exit - --limit LIMIT Limit news topics if this parameter provided - --version Print version info - --json Print result as JSON in stdout - --verbose Outputs verbose status messages - --date DATE Return news from cache with that date. - --to-pdf Conversion of news in the pdf format. - --to- Conversion of news in the ___ format. + -h, --help show this help message and exit + --limit LIMIT Limit news topics if this parameter provided + --version Print version info + --json Print result as JSON in stdout + --verbose Outputs verbose status messages + --date DATE Return news from cache with that date. + --to_pdf TO_PDF Conversion of news in the pdf format. + --to_epub TO_EPUB Conversion of news in the ___ format. ``` - To run the program from the command line, you must write the file name and string with URL-address of news site. It's print a brief description of the news information that appeared on the news site in human-readable @@ -64,8 +63,8 @@ It is also possible to select news from a specific source of a certain date, for and --date that you want. ###What formats can you convert to? How to do it. -Program can convert news in ```--to_pdf``` and ```--to_epub``` format in certain files. -The name of the file generates automatically like 'RSS Reader.pdf/epub'. Also program convert news in ```--json``` format +Program can convert news in ```--to_pdf``` and ```--to_epub``` format. +Specify the path to the file yourself. Also program convert news in ```--json``` format but print result as JSON in stdout. JSON format example: @@ -89,14 +88,16 @@ JSON format example: - *You can convert from cache* For this case news conversion is not depend on internet. -With ```rss_reader.py --date 20191117 --limit 1 --to_pdf``` one news for the specified day -would be converted (the same with ```--to_epub```) and would be generated or overwritten file 'RSS reader.pdf'. +With ```rss_reader.py --date 20191117 --limit 1 --to_pdf /folder1/folder2/file_name.pdf``` one news for the specified day +would be converted (the same with ```--to_epub```) and would be generated or overwritten file 'file_name.pdf' +in folder what you choose. - *You can convert news from the Internet* -For this case Internet is required. With ```rss_reader.py https://news.yahoo.com/rss/ --limit 1 --to_pdf``` one news -from listed link would be converted (the same with ```--to_epub```)and would be generated or overwritten -file 'RSS reader.pdf' +For this case Internet is required. +With ```rss_reader.py https://news.yahoo.com/rss/ --limit 1 --to_pdf /folder1/folder2/file_name.pdf``` one news +from specified link would be converted (the same with ```--to_epub```)and would be generated or overwritten +file 'file_name.epub' in folder what you choose. diff --git a/final_task/rss_reader/NewsCache.py b/final_task/rss_reader/NewsCache.py index b31630c..bdf69df 100644 --- a/final_task/rss_reader/NewsCache.py +++ b/final_task/rss_reader/NewsCache.py @@ -4,6 +4,9 @@ class NewsCache: + """Class which create json-file with cached news, + saved all fresh news and return them with certain date + and from certain url-address if such url will be introduced.""" def __init__(self, file_name): self.logger = logging.getLogger(__name__) @@ -22,6 +25,8 @@ def __init__(self, file_name): self.cache_news = {} def caching(self, all_news, url): + """Function which create JSON-file + and save news.""" for dictionary in all_news: date_in_news = dictionary.pop('Date key') title = dictionary['Title'] @@ -31,6 +36,8 @@ def caching(self, all_news, url): file_to_write.write(json.dumps(self.cache_news)) def returning(self, desired_date, desired_url=None): + """Function which return news from cache-file with certain date + and from certain url-address if such url will be introduced.""" self.logger.info('News search in cache...') caching_news_list = [] if desired_url is not None: diff --git a/final_task/rss_reader/RSSReader.py b/final_task/rss_reader/RSSReader.py index 7c33473..e63019f 100644 --- a/final_task/rss_reader/RSSReader.py +++ b/final_task/rss_reader/RSSReader.py @@ -12,14 +12,14 @@ class RSSReader: - def __init__(self, date, url, limit, is_json, is_pdf, is_epub): + def __init__(self, date, url, limit, is_json, pdf_file_name, epub_file_name): self.cache = NewsCache(CACHE_FILE_NAME) self.date = date self.url = url self.is_json = is_json self.limit = limit - self.is_pdf = is_pdf - self.is_epub = is_epub + self.pdf_file_name = pdf_file_name + self.epub_file_name = epub_file_name self.logger = logging.getLogger(__name__) def url_parsing(self): @@ -107,14 +107,17 @@ def get_news(self): if not string_of_news_dictionaries: self.logger.error('There is no news.') return + file_name = None if self.is_json: converter = 'json' - elif self.is_pdf: + elif self.pdf_file_name: converter = 'pdf' - elif self.is_epub: + file_name = self.pdf_file_name + elif self.epub_file_name: converter = 'epub' + file_name = self.epub_file_name else: self.logger.info('Output news') converter = 'text' output = get_output_function(converter) - output(self.logger, string_of_news_dictionaries, about_website) + output(self.logger, string_of_news_dictionaries, about_website, file_name) diff --git a/final_task/rss_reader/output.py b/final_task/rss_reader/output.py index 4a4e134..cfda4e8 100644 --- a/final_task/rss_reader/output.py +++ b/final_task/rss_reader/output.py @@ -6,6 +6,7 @@ def get_output_function(converter): + """Output factory""" if converter == 'json': return output_json elif converter == 'pdf': @@ -16,7 +17,7 @@ def get_output_function(converter): return output -def output(logger, all_news, about_website=None, ): +def output(logger, all_news, about_website=None, file_name=None): """Function which print information about site and a set of news.""" logger.info('Output news') if about_website is not None: @@ -32,7 +33,8 @@ def parse_to_json(dictionary): return json.dumps(dictionary, indent=4) -def output_json(logger, all_news, about_website=None): +def output_json(logger, all_news, about_website=None, file_name=None): + """Function which print news in stdout in json-format.""" if about_website is not None: logger.info('Convert to JSON-format') print(parse_to_json([about_website] + all_news)) @@ -41,7 +43,8 @@ def output_json(logger, all_news, about_website=None): print(parse_to_json(all_news)) -def output_pdf(logger, all_news, about_website=None): +def output_pdf(logger, all_news, about_website=None, file_name=None): + """Function which create or overwrites PDF-file with selected fresh or cached news""" logger.info('Convert to PDF-format') pdf = FPDF() pdf.add_page() @@ -68,7 +71,7 @@ def output_pdf(logger, all_news, about_website=None): pdf.ln(31) os.remove(filename) except Exception as ex: - logger.error("Error finding image: {}, {}.".format(type(ex), ex)) + logger.warning("Error finding image: {}, {}.".format(type(ex), ex)) pdf.multi_cell(190, 6, txt=f'{key}: {value}', align="L") position_y = pdf.get_y() pdf.set_line_width(1) @@ -76,13 +79,14 @@ def output_pdf(logger, all_news, about_website=None): pdf.line(10, position_y, 200, position_y) logger.info('Creating of PDF-file') try: - pdf.output("RSS news.pdf") + pdf.output(file_name) logger.info('Converted successfully!') except Exception as ex: - logger.error("Error finding image: {}, {}.".format(type(ex), ex)) + logger.error("PDF file writing error : {}, {}.".format(type(ex), ex)) -def output_epub(logger, all_news, about_website=None): +def output_epub(logger, all_news, about_website=None, file_name=None): + """Function which create or overwrites EPUB-file with selected fresh or cached news""" logger.info('Convert to EPUB-format') book = epub.EpubBook() if about_website is not None: @@ -110,7 +114,7 @@ def output_epub(logger, all_news, about_website=None): book.spine = chapters logger.info('Creating of PDF-file') try: - epub.write_epub('RSS news.epub', book, {}) + epub.write_epub(file_name, book, {}) logger.info('Converted successfully!') except Exception as ex: logger.error("Error finding image: {}, {}.".format(type(ex), ex)) diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index 83a7e82..6ee049a 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -22,9 +22,9 @@ def arg_parse(args): help="Outputs verbose status messages") parser.add_argument("--date", type=str, help="Return news from cache with that date.") - parser.add_argument("--to_pdf", action="store_true", + parser.add_argument("--to_pdf", type=str, help="Conversion of news in the pdf format.") - parser.add_argument("--to_epub", action="store_true", + parser.add_argument("--to_epub", type=str, help="Conversion of news in the ___ format.") return parser.parse_args(args) diff --git a/final_task/tests/NewsCache_test.py b/final_task/tests/NewsCache_test.py index e666456..a452a29 100644 --- a/final_task/tests/NewsCache_test.py +++ b/final_task/tests/NewsCache_test.py @@ -23,7 +23,7 @@ "Source of image": "img_link"}}}} -class MyTestCase(unittest.TestCase): +class NewsCacheTestCase(unittest.TestCase): def setUp(self): self.cache = NewsCache('Test_file.json') diff --git a/final_task/tests/RSS_test.py b/final_task/tests/RSS_test.py index 5f50fee..65c4836 100644 --- a/final_task/tests/RSS_test.py +++ b/final_task/tests/RSS_test.py @@ -68,15 +68,15 @@ def test_news_data_collection(self): def test_arg_parse(self): parser = arg_parse(['source', '--limit', '1', '--json', '--verbose', '--version', - '--date', '20191117', '--to_pdf', '--to_epub']) + '--date', '20191117', '--to_pdf', 'pdf_file','--to_epub', 'epub_file']) self.assertTrue(parser.limit == 1) self.assertTrue(parser.source == 'source') self.assertTrue(parser.json) self.assertTrue(parser.verbose) self.assertTrue(parser.version) self.assertTrue(parser.date == '20191117') - self.assertTrue(parser.to_pdf) - self.assertTrue(parser.to_epub) + self.assertTrue(parser.to_pdf == 'pdf_file') + self.assertTrue(parser.to_epub == 'epub_file') if __name__ == '__main__': diff --git a/final_task/tests/output_test.py b/final_task/tests/output_test.py new file mode 100644 index 0000000..3101900 --- /dev/null +++ b/final_task/tests/output_test.py @@ -0,0 +1,55 @@ +import unittest +import sys +from unittest import mock +sys.path.append('../rss_reader') +from output import get_output_function, output, output_json, output_pdf, output_epub + + +TEST_NEWS_LIST = [ + {"Title": "Air racing tournament unveils an all-electric sports aircraft", + "Date": "Sun, 17 Nov 2019 15:35:00 -0500", + "Link": "link", + "Summary": "Test text", + "Source of image": "img_link"}] + + +class OutputTestCase(unittest.TestCase): + + def test_get_output_function(self): + result = get_output_function('text') + self.assertEqual(result, output) + result = get_output_function('json') + self.assertEqual(result, output_json) + result = get_output_function('pdf') + self.assertEqual(result, output_pdf) + result = get_output_function('epub') + self.assertEqual(result, output_epub) + + def test_output(self): + logger = mock.Mock() + with mock.patch('sys.stdout') as mock_print: + output(logger, TEST_NEWS_LIST) + mock_print.assert_has_calls( + [ + mock.call.write('--------------------------------------------------------'), + mock.call.write('\n'), + mock.call.write("Title: Air racing tournament unveils an all-electric sports aircraft"), + mock.call.write('\n'), + mock.call.write("Date: Sun, 17 Nov 2019 15:35:00 -0500"), + mock.call.write('\n'), + mock.call.write("Link: link"), + mock.call.write('\n'), + mock.call.write("Summary: Test text"), + mock.call.write('\n'), + mock.call.write("Source of image: img_link"), + mock.call.write('\n'), + ] + ) + + # + # def test_output_json(self): + # self.assertEqual(True, False) + + +if __name__ == '__main__': + unittest.main() From 3e603d5da6b354060b3808f50e9f9009e3627955 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Mon, 25 Nov 2019 22:28:49 +0300 Subject: [PATCH 08/10] Fifth iteration --- final_task/README.md | 5 +++++ final_task/rss_reader/RSSReader.py | 5 ++++- final_task/rss_reader/output.py | 23 +++++++++++++++++++++++ final_task/rss_reader/requirements.txt | 3 ++- final_task/rss_reader/rss_reader.py | 8 +++++--- final_task/setup.py | 2 +- final_task/tests/RSS_test.py | 5 +++-- final_task/tests/output_test.py | 26 ++++++++++++++++++++------ 8 files changed, 63 insertions(+), 14 deletions(-) diff --git a/final_task/README.md b/final_task/README.md index fcd7d22..bf20893 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -54,6 +54,11 @@ Source of image: http://l2.yimg.com/uu/api/res/1.2/3h5JxcDjAP1pHR6KUD2JMQ--/YXBw aWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en-US/business_insider_ articles_888/7a21ff2ee09c3b4285094c4e64e9602c ``` +###Nice features +You can To please the eyes you can read the news from console in a colorful format. +For this enter such parameter like --colorize and enjoy. + +But this parameter work only for MAC and Linux console. ###How to read saving news: If you want to read saved news of a certain date, you must enter a --date in %Y%m%d format and the program will work diff --git a/final_task/rss_reader/RSSReader.py b/final_task/rss_reader/RSSReader.py index e63019f..f9b4e32 100644 --- a/final_task/rss_reader/RSSReader.py +++ b/final_task/rss_reader/RSSReader.py @@ -12,7 +12,7 @@ class RSSReader: - def __init__(self, date, url, limit, is_json, pdf_file_name, epub_file_name): + def __init__(self, date, url, limit, is_json, pdf_file_name, epub_file_name, color): self.cache = NewsCache(CACHE_FILE_NAME) self.date = date self.url = url @@ -20,6 +20,7 @@ def __init__(self, date, url, limit, is_json, pdf_file_name, epub_file_name): self.limit = limit self.pdf_file_name = pdf_file_name self.epub_file_name = epub_file_name + self.color = color self.logger = logging.getLogger(__name__) def url_parsing(self): @@ -116,6 +117,8 @@ def get_news(self): elif self.epub_file_name: converter = 'epub' file_name = self.epub_file_name + elif self.color: + converter = 'color' else: self.logger.info('Output news') converter = 'text' diff --git a/final_task/rss_reader/output.py b/final_task/rss_reader/output.py index cfda4e8..0b02197 100644 --- a/final_task/rss_reader/output.py +++ b/final_task/rss_reader/output.py @@ -1,6 +1,7 @@ import os import json import urllib +from colored import attr, fg, bg from ebooklib import epub from fpdf import FPDF @@ -13,10 +14,32 @@ def get_output_function(converter): return output_pdf elif converter == 'epub': return output_epub + elif converter == 'color': + return colored_output elif converter == 'text': return output +def colored_output(logger, all_news, about_website=None, file_name=None): + """Function which print information about site and a set of news with multi-colored text.""" + logger.info('Output multi-colored news =)') + red = fg(1) + black = fg(0) + green = fg(28) + blue = fg(21) + purple = fg(55) + background_site = bg(225) + background_news = bg(195) + reset = attr(0) + if about_website is not None: + for key, value in about_website.items(): + print(f'{background_site}\n{red}{key}: {purple}{value}{reset}') + for number_of_news in all_news: + print(f"{background_news}{black}--------------------------------------------------------{reset}") + for key, value in number_of_news.items(): + print(f'{background_news}{green}{key}: {blue}{value}{reset}') + + def output(logger, all_news, about_website=None, file_name=None): """Function which print information about site and a set of news.""" logger.info('Output news') diff --git a/final_task/rss_reader/requirements.txt b/final_task/rss_reader/requirements.txt index 6d32dcb..4cf00db 100644 --- a/final_task/rss_reader/requirements.txt +++ b/final_task/rss_reader/requirements.txt @@ -2,4 +2,5 @@ bs4 feedparser unidecode fpdf -ebooklib \ No newline at end of file +ebooklib +colored \ No newline at end of file diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index 6ee049a..3b3c9c3 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -4,7 +4,7 @@ from RSSReader import RSSReader -VERSION = '4.0' +VERSION = '5.0' def arg_parse(args): @@ -25,7 +25,9 @@ def arg_parse(args): parser.add_argument("--to_pdf", type=str, help="Conversion of news in the pdf format.") parser.add_argument("--to_epub", type=str, - help="Conversion of news in the ___ format.") + help="Conversion of news in the epub format.") + parser.add_argument("--colorize", action="store_true", + help="Print the result of the utility in colorized mode") return parser.parse_args(args) @@ -36,7 +38,7 @@ def main(): if args.version: print('RSS-reader version {}'.format(VERSION)) # program version call return - reader = RSSReader(args.date, args.source, args.limit, args.json, args.to_pdf, args.to_epub) + reader = RSSReader(args.date, args.source, args.limit, args.json, args.to_pdf, args.to_epub, args.colorize) reader.get_news() diff --git a/final_task/setup.py b/final_task/setup.py index 01540b7..884c192 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -5,7 +5,7 @@ name='rss_reader', version=VERSION, packages=find_packages(), - install_requires=['feedparser', 'bs4', 'unidecode', 'fpdf', 'ebooklib'], + install_requires=['feedparser', 'bs4', 'unidecode', 'fpdf', 'ebooklib', 'colored'], author="Victoria Kondrat'eva", author_email='sam.kondrateva@gmail.com', url='https://github.com/Victoria-Sam', diff --git a/final_task/tests/RSS_test.py b/final_task/tests/RSS_test.py index 65c4836..9fd6f72 100644 --- a/final_task/tests/RSS_test.py +++ b/final_task/tests/RSS_test.py @@ -37,7 +37,7 @@ class RSSTestCase(unittest.TestCase): def setUp(self): - self.rss = RSSReader(None, None, None, None, None, None) + self.rss = RSSReader(None, None, None, None, None, None, None) def test_make_news_data(self): result = self.rss.make_news_data(RSS_DICT_TEST['entries'][0]) @@ -68,7 +68,7 @@ def test_news_data_collection(self): def test_arg_parse(self): parser = arg_parse(['source', '--limit', '1', '--json', '--verbose', '--version', - '--date', '20191117', '--to_pdf', 'pdf_file','--to_epub', 'epub_file']) + '--date', '20191117', '--to_pdf', 'pdf_file','--to_epub', 'epub_file', '--colorize']) self.assertTrue(parser.limit == 1) self.assertTrue(parser.source == 'source') self.assertTrue(parser.json) @@ -77,6 +77,7 @@ def test_arg_parse(self): self.assertTrue(parser.date == '20191117') self.assertTrue(parser.to_pdf == 'pdf_file') self.assertTrue(parser.to_epub == 'epub_file') + self.assertTrue(parser.colorize) if __name__ == '__main__': diff --git a/final_task/tests/output_test.py b/final_task/tests/output_test.py index 3101900..d6740bd 100644 --- a/final_task/tests/output_test.py +++ b/final_task/tests/output_test.py @@ -2,11 +2,11 @@ import sys from unittest import mock sys.path.append('../rss_reader') -from output import get_output_function, output, output_json, output_pdf, output_epub +from output import get_output_function, output, output_json, output_pdf, output_epub, parse_to_json TEST_NEWS_LIST = [ - {"Title": "Air racing tournament unveils an all-electric sports aircraft", + {"Title": "Some title", "Date": "Sun, 17 Nov 2019 15:35:00 -0500", "Link": "link", "Summary": "Test text", @@ -33,7 +33,7 @@ def test_output(self): [ mock.call.write('--------------------------------------------------------'), mock.call.write('\n'), - mock.call.write("Title: Air racing tournament unveils an all-electric sports aircraft"), + mock.call.write("Title: Some title"), mock.call.write('\n'), mock.call.write("Date: Sun, 17 Nov 2019 15:35:00 -0500"), mock.call.write('\n'), @@ -46,9 +46,23 @@ def test_output(self): ] ) - # - # def test_output_json(self): - # self.assertEqual(True, False) + def test_output_json(self): + logger = mock.Mock() + with mock.patch('sys.stdout') as mock_print: + output_json(logger, TEST_NEWS_LIST) + mock_print.assert_has_calls( + [ + mock.call.write('[\n ' + '{\n "Title": "Some title",' + '\n "Date": "Sun, 17 Nov 2019 15:35:00 -0500",' + '\n "Link": "link",' + '\n "Summary": "Test text",' + '\n "Source of image": "img_link"' + '\n }' + '\n]'), + mock.call.write('\n'), + ] + ) if __name__ == '__main__': From 1710df7f7f594097c9a87d35221f4b388971ca5b Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Mon, 25 Nov 2019 22:36:24 +0300 Subject: [PATCH 09/10] fixed README --- final_task/README.md | 10 ++++++---- final_task/rss_reader/rss_reader.py | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/final_task/README.md b/final_task/README.md index bf20893..8ef66bc 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -9,6 +9,7 @@ The recommended way to install rss-reader is with pip: ``` usage: rss_reader.py [-h] [--limit LIMIT] [--version] [--json] [--verbose] [--date DATE] [--to_pdf TO_PDF] [--to_epub TO_EPUB] + [--colorize] [source] Pure Python command-line RSS reader. @@ -22,9 +23,10 @@ optional arguments: --version Print version info --json Print result as JSON in stdout --verbose Outputs verbose status messages - --date DATE Return news from cache with that date. - --to_pdf TO_PDF Conversion of news in the pdf format. - --to_epub TO_EPUB Conversion of news in the ___ format. + --date DATE Return news from cache with that date + --to_pdf TO_PDF Conversion of news in the pdf format + --to_epub TO_EPUB Conversion of news in the epub format + --colorize Print news in colorized mode in stdout ``` To run the program from the command line, you must write the file name and string with URL-address of news site. @@ -55,7 +57,7 @@ aWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en-US/business_insider_ articles_888/7a21ff2ee09c3b4285094c4e64e9602c ``` ###Nice features -You can To please the eyes you can read the news from console in a colorful format. +To please the eyes you can read news from console in a colorful format. For this enter such parameter like --colorize and enjoy. But this parameter work only for MAC and Linux console. diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index 3b3c9c3..75204c4 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -21,13 +21,13 @@ def arg_parse(args): parser.add_argument("--verbose", action="store_true", help="Outputs verbose status messages") parser.add_argument("--date", type=str, - help="Return news from cache with that date.") + help="Return news from cache with that date") parser.add_argument("--to_pdf", type=str, - help="Conversion of news in the pdf format.") + help="Conversion of news in the pdf format") parser.add_argument("--to_epub", type=str, - help="Conversion of news in the epub format.") + help="Conversion of news in the epub format") parser.add_argument("--colorize", action="store_true", - help="Print the result of the utility in colorized mode") + help="Print news in colorized mode in stdout") return parser.parse_args(args) From 172c095199a3c5370eee86c5e65e0b0e75017958 Mon Sep 17 00:00:00 2001 From: Victoria-Sam Date: Mon, 25 Nov 2019 23:47:13 +0300 Subject: [PATCH 10/10] last minutes...fixed module import --- final_task/README.md | 4 ++-- final_task/rss_reader/rss_reader.py | 3 +++ final_task/setup.py | 10 ++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/final_task/README.md b/final_task/README.md index 8ef66bc..001e0cf 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -2,8 +2,8 @@ Program is named rss_reader.py. ##Instalation The recommended way to install rss-reader is with pip: -* pip install rss_reader - for Windows -* sudo pip install rss_reader - for Linux and Mac +* pip install rss_reader-5.0.tar.gz - for Windows +* sudo pip install rss_reader-5.0.tar.gz - for Linux and Mac ##Description and Functions ``` diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index 75204c4..26f5d3f 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -1,6 +1,9 @@ import sys +import os import argparse import logging +dir_add = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(dir_add) from RSSReader import RSSReader diff --git a/final_task/setup.py b/final_task/setup.py index 884c192..d1fbf38 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,9 +1,11 @@ +import sys +sys.path.append('rss_reader') from setuptools import setup, find_packages -from rss_reader/rss_reader import VERSION +from rss_reader import rss_reader setup( name='rss_reader', - version=VERSION, + version=rss_reader.VERSION, packages=find_packages(), install_requires=['feedparser', 'bs4', 'unidecode', 'fpdf', 'ebooklib', 'colored'], author="Victoria Kondrat'eva", @@ -12,5 +14,5 @@ description='RSS Reader', entry_points={'console_scripts': ['Rss-Reader = rss_reader.rss_reader:main']}, keywords="rss-reader", - python_requires = '>=3.7', -) \ No newline at end of file + python_requires='>=3.7', +)