diff --git a/README.md b/README.md index c86d1e65..67964c8b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,116 @@ -# How to create a PR with a homework task - -1. Create fork from the following repo: https://github.com/E-P-T/Homework. (Docs: https://docs.github.com/en/get-started/quickstart/fork-a-repo ) -2. Clone your forked repo in your local folder. -3. Create separate branches for each session.Example(`session_2`, `session_3` and so on) -4. Create folder with you First and Last name in you forked repo in the created session. -5. Add your task into created folder -6. Push finished session task in the appropriate branch in accordance with written above. - You should get the structure that looks something like that - -``` - Branch: Session_2 - DzmitryKolb - |___Task1.py - |___Task2.py - Branch: Session_3 - DzmitryKolb - |___Task1.py - |___Task2.py -``` - -7. When you finish your work on task you should create Pull request to the appropriate branch of the main repo https://github.com/E-P-T/Homework (Docs: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). -Please use the following instructions to prepare good description of the pull request: - - Pull request header should be: `Session - `. - Example: `Session 2 - Dzmitry Kolb` - - Pull request body: You should write here what tasks were implemented. - Example: `Finished: Task 1.2, Task 1.3, Task 1.6` + _ __ ___ ___ _ __ ___ __ _ __| | ___ _ __ +| '__|/ __|/ __| | '__| / _ \ / _` | / _` | / _ \| '__| +| | \__ \\__ \ | | | __/| (_| || (_| || __/| | +|_| |___/|___/ _____ |_| \___| \__,_| \__,_| \___||_| + |_____| + +Welcome to rss_reader.py readme file! + +Tested on Windows 10! + +usage: rss_reader.py [-h] [--date DATE] [-v] [--verbose] [--to-html] [--to-epub] [--json] [--limit LIMIT] [source] + +This program gets information from RSS-channel and returns in user friendly format. + +positional arguments: + source RSS link for your information + +options: + -h, --help show this help message and exit + --date DATE Get news form the database by date. + -v, --version Print program version and exit. + --verbose Outputs verbose status messages. + --to-html Return HTML file to C:\rss_reader\html_files + --to-epub Return HTML file to C:\rss_reader\epub_files + --json Print result as JSON in stdout. + --limit LIMIT Limit news topics if this parameter provided. + + + +source is a positional argument that you should input to your program, when you have to get information from the RSS-channel. + +If --limit parameter takes only integer numbers which provide program to return that number of news. +If that parameter not provided, program print all news from RSS-channel. + + +--json is a parameter that print json string in format that described below. This parameter also takes effect from --limit parameter. + +"[title of the rss site where you get news]": [ + { + "Title": "[date of that fact[1]]", + "Link": "[link to the fact[1]]", + "Date": "[date of that fact[1]]", + "Description": "[fact's [1] short summary]" + }, + ..........., + { + "Title": "[date of that fact[limit]]", + "Link": "[link to the fact[limit]]", + "Date": "[date of that fact[limit]]", + "Description": "[fact's [limit] short summary]" + } + ] +} + +If --json parameter not provided, program print to console news in format below. + +Feed:[title of the rss site where you get news] + +Title: [fact [1] title] +Date: [date of that fact[1]] +Link: [link to the fact[1]] +Description: [fact's [1] short summary] +...... +Title: [fact [limit] title] +Date: [date of that fact[limit]] +Link: [link to the fact[limit]] +Description: [fact's [limit] short summary] + +--to-html is parameter that saves information to C:\rss_reader\html_files folder with given name in date time format `%d%m%y%H%M`. + +--to-epub is parameter that saves information to C:\rss_reader\epub_files folder with given name in date time format `%d%m%y%H%M`. + +--to-epub and --to-html also works with other parameters. + +--date is a parameter to get information from the database. This parameter should take a date in `%Y%m%d` format. +For example: `--date 20191020` +With --to-epub or --to-html parameter program saves html or epub files to corresponding folders with `%Y%m%d` nameformat. +--date with source parameter returns data from database according to date and source link. +Just --date parameter returns to console news in format described below: + +News on date [date]! + +Title: [fact [1] title] +Date: [date of that fact[1]] +Link: [link to the fact[1]] +Description: [fact's [1] short summary] +...... +Title: [fact [limit] title] +Date: [date of that fact[limit]] +Link: [link to the fact[limit]] +Description: [fact's [limit] short summary] + + +With --json parameter returns JSON string: + +"[date]": [ + { + "Title": "[date of that fact[1]]", + "Link": "[link to the fact[1]]", + "Date": "[date of that fact[1]]", + "Description": "[fact's [1] short summary]" + }, + ..........., + { + "Title": "[date of that fact[limit]]", + "Link": "[link to the fact[limit]]", + "Date": "[date of that fact[limit]]", + "Description": "[fact's [limit] short summary]" + } + ] +} + +Running unittests for rss_reader: +py -m unittest rss_reader/dbh_tests.py +py -m unittest rss_reader/tests.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e848f78c Binary files /dev/null and b/requirements.txt differ diff --git a/rss_reader/__init__.py b/rss_reader/__init__.py new file mode 100644 index 00000000..ac65caad --- /dev/null +++ b/rss_reader/__init__.py @@ -0,0 +1,3 @@ +from .rss_reader import * +from .tests import * +from .database_handler import * \ No newline at end of file diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py new file mode 100644 index 00000000..369e5c9b --- /dev/null +++ b/rss_reader/__main__.py @@ -0,0 +1,5 @@ +from rss_reader import main + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/rss_reader/database_handler.py b/rss_reader/database_handler.py new file mode 100644 index 00000000..bc0012e3 --- /dev/null +++ b/rss_reader/database_handler.py @@ -0,0 +1,176 @@ +import sqlite3 +import time +import os +import logging as log +import json + +data_base = "news_data.db" + +class DataBaseHandler: + + """Class that handles with sqlite3 database. With that class you can: + adress to your DB through context manager, create table, input data to table, + get data from table by date, souce, check emtyness of the table.""" + + def __init__(self, base_path): + log.info("Initialization of object complieted.") + self.path = base_path + + def __enter__(self): + log.info("Connection to database esteblished.") + self.conn = sqlite3.connect(self.path) + return self.conn + + def __exit__(self, exc_type, exc_val, exc_tb): + log.info("Connection closed") + if exc_type is None: + self.conn.close() + + def create_table(self): + + """Metod that help us to create table in our database if it doesn't exist. + Table contains next fields: date TEXT, + source TEXT, + title TEXT, + url TEXT, + full_date TEXT, + description TEXT""" + + with DataBaseHandler(self.path) as conn: + cursor = conn.cursor() + cursor.execute("""CREATE TABLE IF NOT EXISTS news_data ( + date TEXT, + source TEXT, + title TEXT, + url TEXT, + full_date TEXT, + description TEXT)""") + conn.commit() + log.info("Now news_data table exists in out database.") + + + def emptiness_checker(self): + + """This metod check our database and return boolean result. + If it is empty - return True, else return - False""" + + log.info("Cheking table for emptiness...") + with DataBaseHandler(self.path) as conn: + cursor = conn.cursor() + cursor.execute("""SELECT COUNT(*) FROM news_data""") + result = cursor.fetchone() + if result[0] == 0: + log.info("Table is empty!") + return True + else: + log.info("There is data in the table!") + return False + + def add_data(self, source, *args): + + """This method add information to news_data table, + also this method creates information to date field.""" + + converted_date = time.strptime(args[2], "%a, %d %b %Y %H:%M:%S %z") + date = time.strftime("%Y%m%d", converted_date) + with DataBaseHandler(self.path) as conn: + cursor = conn.cursor() + cursor.execute("""INSERT INTO news_data (date, source, title, url, full_date, description) + VALUES (?, ?, ?, ?, ?, ?)""", (date, source, args[0], args[1], args[2], args[3])) + conn.commit() + log.info("Data added to the news_data table.") + + def retrieve_data(self, date, source=None, num=None): + + """If num parameter specified, takes numered quantity of data, + from database especially by date, and by source if it specified.""" + + with DataBaseHandler(self.path) as conn: + cursor = conn.cursor() + if source is None: + cursor.execute("""SELECT title, url, full_date, description FROM news_data WHERE date=?""", (date,)) + else: + cursor.execute("""SELECT title, url, full_date, description + FROM news_data WHERE date=? AND source=?""", (date, source)) + conn.commit() + data = cursor.fetchall() + if len(data) == 0: + log.info("There is no such data in the table.") + raise ValueError + if num is None: + self.data = data + log.info("Provided amount of data retrieved from the table.") + else: + self.data = data[:num] + log.info("Provided amount of data retrieved from the table.") + return self.data + + def data_to_json(self, date): + + """Returns retrieved data from database in json format""" + + log.info("Collecting data in json!") + self.json_data = {date:[]} + for i in self.data: + fact_dict = {"Title": i[0], + "Link": i[1], + "Date": i[2], + "Description": i[3], + } + self.json_data[date].append(fact_dict) + log.info("Collecting data to json complieted!") + return json.dumps(self.json_data, ensure_ascii=False, indent=4) + + def data_to_print(self, date): + + """Prints retrieved data from database to console.""" + + log.info("Printing data from database.") + print(f"News on {date}!\n") + for i in self.data: + print(f"Title: {i[0]}") + print(f"Date: {i[2]}") + print(f"Link: {i[1]}") + print(f"Description: {i[3]}", end="\n") + print('\n') + + def data_to_html(self, date): + + """Metod that costract HTML text from data retrieved from database. + Takes input date as a title and header.""" + + header = f""" + + + + {date} + + """ + + end = """ + """ + + log.info("Stating construction HTML file!") + self.html_text = header + + h1 = f"""

News on: {date}

""" + self.html_text += h1 + for inf in self.data: + p = f"""

+ Title: {inf[0]}
+ Date: {inf[2]}
+ Link: clickable link
+ Description: {inf[3]}
+


+

""" + self.html_text += p + self.html_text += end + log.info("Construction HTML file complieted!") + return self.html_text + + + + + + + diff --git a/rss_reader/dbh_tests.py b/rss_reader/dbh_tests.py new file mode 100644 index 00000000..39234bc3 --- /dev/null +++ b/rss_reader/dbh_tests.py @@ -0,0 +1,23 @@ +import unittest +from database_handler import * +from unittest.mock import patch, Mock + + +class MyTest(unittest.TestCase): + @patch("database_handler.DataBaseHandler") + def test_create_table(self, mock_DataBase): + m = mock_DataBase + m.create_table() + m.create_table.assert_called() + + @patch("database_handler.DataBaseHandler") + def test_emptiness_checker(self, mock_DataBase): + m = mock_DataBase + m.emptiness_checker() + m.emptiness_checker.assert_called() + + + + +if __name__ == '__main__': + unittest.main(argv=[''], exit=False) \ No newline at end of file diff --git a/rss_reader/rss_reader.py b/rss_reader/rss_reader.py new file mode 100644 index 00000000..4f26a8db --- /dev/null +++ b/rss_reader/rss_reader.py @@ -0,0 +1,394 @@ +import requests +from lxml import etree +import xml.etree.ElementTree as elementTree +from bs4 import BeautifulSoup +import time +from dateutil import parser +import json +import logging as log +import argparse +import os +import rss_reader.database_handler as handler +from datetime import datetime +from ebooklib import epub + + + +def get_content(url: str): + + """Get request from the url and depending on a flag returns corresponding xml etree or html soup""" + + log.info(f"Try in to retrieve information from {url}") + try: + r = requests.get(url) + log.info(f"Connection to {url} established!") + if is_xml(r.content): + root = etree.fromstring(r.content) + log.info("XML content downloaded and parsed.") + return root[0] + else: + raise AttributeError() + except requests.exceptions.RequestException as exc: + print(f"Error Occurred: {exc}") + except AttributeError: + print("Site that you entered is not RSS, because it doesn't contain XML!") + + +def is_xml(value): + + """Check value containing XML content""" + + try: + elementTree.fromstring(value) + except elementTree.ParseError: + log.info("Received data isn't XML.") + return False + else: + log.info("Received data is XML content.") + return True + + +def get_feed(root): + + """Return feed string""" + + feed = root.find("title").text + return feed + + +def get_news_db(root, num=None): + + """Get all "item" elements and return list or slice of the list from root if num specified.""" + + news_db = root.findall("item") + + if num is not None: + news_slice = news_db[:num] + log.info("Retrieved user defined number of news.") + return news_slice + else: + log.info("Retrieved all amount of news.") + return news_db + + +def inf_generator(news_db): + + """Get information from the "item" list and yield it as a list + next format [title, link, date, description]""" + + log.info("Retrieving information from item branches.") + for bunch in news_db: + title = bunch.find("title").text + link = link_handler(bunch.find("link")) + date = date_handler(bunch.find("pubDate")) + description = description_handler(bunch.find("description")) + inf_lst = [title, link, date, description] + log.info("Branch retrieved successful!") + yield inf_lst + + +def date_handler(date_inf): + + """Return date in weekday, daynumber month year hours:minutes:seconds timezone""" + + if date_inf is None: + log.info("RSS doesn't provide date field!") + return "Date field doesn't provided!" + else: + date = date_inf.text + pure_date = parser.parse(date) + return_date = pure_date.strftime("%a, %d %b %Y %H:%M:%S %z") + log.info("Date retrieved successfully!") + return return_date + + +def link_handler(inf): + + """Handles absence of link in RSS.""" + + if inf is None: + log.info("RSS doesn't provide news link!") + return "Link field doesn't provided!" + else: + log.info("Link retrieved successful.") + link = inf.text + return link + + +def description_handler(inf): + + """Handles absence of link in RSS and gets HTML from the string.""" + + if inf is None: + log.info("RSS doesn't provide description!") + return "There is no description!" + else: + description = inf.text + if description is None: + log.info("RSS doesn't provide description!") + return "There is no description" + elif "<" in description: + soup = BeautifulSoup(description, "html.parser") + data = soup.text + log.info("Description retrieved successful from HTML text.") + return data + else: + log.info("Description retrieved successful.") + return description + + +def printer(feed, news_db): + + """Printing in the console news in next format: + Feed:[title of the rss site where you get news] + + Title: [fact's title] + Date: [date of that fact] + Link: [link to the fact] + Description: [fact's short summary] + """"" + + log.info("Printing information to console!") + print(f"Feed: {feed}", "\n") + gen = inf_generator(news_db) + for inf in gen: + print(f"Title: {inf[0]}") + print(f"Date: {inf[2]}") + print(f"Link: {inf[1]}") + print(f"Description: {inf[3]}", end="\n") + print('\n') + + +def news_to_json(feed, news_db): + + """Collect information form the site into dict "news feed" and return it as a json string""" + + log.info("Collection information to json format.") + news_feed = {feed: []} + gen = inf_generator(news_db) + for inf in gen: + fact_dict = {"Title": inf[0], + "Link": inf[1], + "Date": inf[2], + "Description": inf[3], + } + news_feed[feed].append(fact_dict) + log.info("Collecting JSON completed successful!") + return json.dumps(news_feed, ensure_ascii=False, indent=4) + + +def output_form(main_title, news_data, form): + + """Printing return the output to console if form specified (console/json)""" + + if form not in ["console", "json"]: + log.error("Entered unavailable format!") + raise AttributeError("Choose between console or json!") + elif form == "console": + log.info("Printing information to console....") + printer(main_title, news_data) + elif form == "json": + log.info("Printing json information to console....") + news_json = news_to_json(main_title, news_data) + print(news_json) + + +def create_path(path): + + """Creates path if it not exist""" + + if not os.path.exists(path): + os.mkdir(path) + log.info(f"Path {path} created") + + +def html_adder(nw_title, db, head=None): + + """Creates HTML text from database and title.""" + + log.info("Start creating HTML text!") + if head is None: + head = datetime.now().strftime("%d%m%y %H:%M") + log.info("Created head for HTML text!") + + header = f""" + + + + {head} + + """ + + end = """ + """ + + html_text = header + + h1 = f"""

Feed: {nw_title}

""" + html_text += h1 + gen = inf_generator(db) + for inf in gen: + p = f"""

+ Title: {inf[0]}
+ Link: clickable link
+ Date: {inf[2]}
+ Description: {inf[3]}
+


+

""" + html_text += p + html_text += end + log.info("HTML text creation complieted!") + return html_text + +def write_html_file(path, html_text, html_name=None): + + """Write HTML text to file, than saves file to the path, + also this programm automatically gives name to files.""" + + log.info("Starting writing HTML to the file!") + if html_name is None: + html_name = datetime.now().strftime("%d%m%y%H%M") + ".html" + log.info("Created name for HTML file!") + if not html_name.endswith(".html"): + html_name = html_name + ".html" + log.info("Added .html extention to file name!") + full_path = os.path.join(path, html_name) + with open(full_path, "w", encoding="utf-8") as file: + file.write(html_text) + log.info("HTML file created!") + print(f"HTML file saved to to path: {full_path}") + + +def write_epub_file(path, html_text, epub_name=None): + + """Write ePub file, than saves file to the path, + also this programm automatically gives name to files.""" + + log.info("Starting writing HTML to the ePub file!") + if epub_name is None: + epub_name = datetime.now().strftime("%d%m%y%H%M") + ".epub" + log.info("Created name for ePub file!") + if not epub_name.endswith(".epub"): + epub_name = epub_name + ".epub" + log.info("Added .epub extention to file name!") + file_path = os.path.join(path, epub_name) + book = epub.EpubBook() + book.add_author('Nurmatov Farrukh') # you found easter egg + c1 = epub.EpubHtml(title='News', file_name='chap_01.xhtml') + c1.content = html_text + book.add_item(c1) + book.add_item(epub.EpubNcx()) + book.add_item(epub.EpubNav()) + style = 'BODY {color: white;}' + nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) + book.add_item(nav_css) + book.spine = ['nav', c1] + epub.write_epub(file_path, book, {}) + log.info("ePub file created!") + print(f"Epub file saved to: {file_path}") + + +def main(): + parser = argparse.ArgumentParser(description="This program gets information from RSS-channel " + "and returns in user friendly format.") + parser.add_argument("source", type=str, nargs="?", help="RSS link for your information", default=None) + parser.add_argument("--date", type=str, help="Get news form the database by date.") + parser.add_argument("-v", "--version", action="version", version="Version 1.4.0", + help="Print program version and exit.") + parser.add_argument("--verbose", action="store_true", help="Outputs verbose status messages.") + parser.add_argument("--to-html", action="store_true", help="Return HTML file to C:\\rss_reader\\html_files", + dest="to_html") + parser.add_argument("--to-epub", action="store_true", help="Return HTML file to C:\\rss_reader\\epub_files", + dest="to_epub") + parser.add_argument("--json", action="store_true", help="Print result as JSON in stdout.") + parser.add_argument("--limit", type=int, help="Limit news topics if this parameter provided.") + + args = parser.parse_args() + rss_url = args.source + date = args.date + verbose = args.verbose + html = args.to_html + epub = args.to_epub + print_json = args.json + limit = args.limit + + parent_dir = "C:/" + root_dir = "rss_reader" + db_dir = "data_base" + html_dir = "html_files" + epub_dir = "epub_files" + + if verbose: + log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) + log.info("Verbose output.") + else: + log.basicConfig(format="%(levelname)s: %(message)s") + + sys_path = os.path.join(parent_dir, root_dir) + create_path(sys_path) + db_path = os.path.join(sys_path, db_dir) + create_path(db_path) + html_path = os.path.join(sys_path, html_dir) + create_path(html_path) + epub_path = os.path.join(sys_path, epub_dir) + create_path(epub_path) + db = os.path.join(db_path, handler.data_base) + base = handler.DataBaseHandler(db) + base.create_table() + + try: + if date is not None: + if base.emptiness_checker(): + raise AttributeError + base_inf = base.retrieve_data(date, rss_url, limit) + html_data = base.data_to_html(date) + if print_json and html: + print(base.data_to_json(date)) + write_html_file(html_path, html_data, date) + elif print_json and epub: + print(base.data_to_json(date)) + write_epub_file(epub_path, html_data, date) + elif print_json: + print(base.data_to_json(date)) + elif html: + write_html_file(html_path, html_data, date) + base.data_to_print(date) + elif epub: + write_epub_file(epub_path, html_data, date) + base.data_to_print(date) + else: + base.data_to_print(date) + else: + xml_content = get_content(rss_url) + feed = get_feed(xml_content) + news_db = get_news_db(xml_content, limit) + sql_gen = inf_generator(news_db) + html_inf = html_adder(feed, news_db) + + for inf in sql_gen: + base.add_data(rss_url, *inf) + + if print_json and html: + output_form(feed, news_db, "json") + write_html_file(html_path, html_inf) + elif print_json and epub: + output_form(feed, news_db, "json") + write_epub_file(epub_path, html_inf) + elif print_json: + output_form(feed, news_db, "json") + elif html: + write_html_file(html_path, html_inf) + output_form(feed, news_db, "console") + elif epub: + write_epub_file(epub_path, html_inf) + output_form(feed, news_db, "console") + else: + output_form(feed, news_db, "console") + except ValueError: + print(f"There is no data in database on date {date}.") + except AttributeError: + print(f"There is not data in the database. You should fill the database first.") + + +if __name__ == "__main__": + main() diff --git a/rss_reader/tests.py b/rss_reader/tests.py new file mode 100644 index 00000000..b1e62e1b --- /dev/null +++ b/rss_reader/tests.py @@ -0,0 +1,120 @@ +import unittest +from unittest.mock import patch, Mock, MagicMock +import rss_reader +from rss_reader import * +import lxml +import os + + +class MyTest(unittest.TestCase): + def test_is_xml(self): + r = requests.get("https://news.yahoo.com/rss/") + xml_content = r.content + self.assertFalse(is_xml(xml_content)) + self.assertTrue(is_xml(xml_content)) + + def test_is_xml_2(self): + s = requests.get("https://mail.ru") + c = s.content + self.assertTrue(is_xml(c)) + self.assertFalse(is_xml(c)) + + def test_get_content(self): + r = requests.get("https://news.yahoo.com/rss/") + xml_content = r.content + root = etree.fromstring(xml_content) + self.assertEqual(type(get_content("https://news.yahoo.com/rss/")), type(root[0])) + + @patch('rss_reader.get_content') + def test_for_get_content(self, mock_is_xml): + mock_is_xml.return_value = False + with self.assertRaises(Exception): + get_content("https://news.yahoo.com/rss/") + mock_is_xml.assert_called_once() + + def test_link_handler(self): + link_xml = "https://news.yahoo.com/gop-commission-refuses-certify-mexico-004011875.html" + root = etree.fromstring(link_xml) + link = "https://news.yahoo.com/gop-commission-refuses-certify-mexico-004011875.html" + self.assertEqual(link_handler(None), "Link field doesn't provided!") + self.assertEqual(link_handler(root), link) + + def test_description_handler(self): + description = '' \ + '

В МЧС рассказали об аварии, которая произошла 14 июня ' \ + 'около 17:30 на трассе М1 (Брест — Минск — граница России), вблизи деревни Рябиновка Дзержинского района. Mazda 626 столкнулась с микроавтобусом Mercedes Sprinter.' \ + '

Читать далее…

]]>
' + root = etree.fromstring(description) + clear_descr = " В МЧС рассказали об аварии, которая произошла 14 июня около 17:30 на трассе М1 (Брест — Минск — граница России), " \ + "вблизи деревни Рябиновка Дзержинского района. Mazda 626 столкнулась с микроавтобусом Mercedes Sprinter.Читать далее… " + self.assertEqual(description_handler(root), clear_descr) + self.assertEqual(description_handler(None), "There is no description!") + description_2 = "Graphic footage of the attack in China set off a heated debate that showed both " \ + "the growing awareness of women’s rights and how divisive feminism still remains." + clear_descr_2 = "Graphic footage of the attack in China set off a heated debate that showed both " \ + "the growing awareness of women’s rights and how divisive feminism still remains." + root_2 = etree.fromstring(description_2) + self.assertEqual(description_handler(root_2), clear_descr_2) + + + def test_date_handler(self): + date_inf_1 = Mock() + date_inf_1.text = "Fri, 24 Jun 2022 07:18:35 +0000" + date_inf_2 = Mock() + date_inf_2.text = "Fri, 24 Jun 2022 05:36:37 GMT" + date_inf_3 = Mock() + date_inf_3.text = "2022-06-24T05:39:45Z" + self.assertEqual(date_handler(date_inf_1), "Fri, 24 Jun 2022 07:18:35 +0000") + self.assertEqual(date_handler(date_inf_2), "Fri, 24 Jun 2022 05:36:37 +0000") + self.assertEqual(date_handler(date_inf_3), "Fri, 24 Jun 2022 05:39:45 +0000") + self.assertEqual(date_handler(None), "Date field doesn't provided!") + + def test_link_handler(self): + inf = Mock() + inf.text = "https://www.google.com" + self.assertEqual(link_handler(inf), "https://www.google.com") + self.assertEqual(link_handler(None), "Link field doesn't provided!") + + def test_description_handler(self): + self.assertEqual(description_handler(None), "There is no description!") + inf_1 = Mock() + inf_1.text = None + self.assertEqual(description_handler(inf_1), "There is no description") + inf_2 = Mock() + inf_2.text = '

Test information

' + self.assertEqual(description_handler(inf_2), "Test information") + + @patch("os.path.exists") + @patch("os.mkdir") + def test_create_path(self, mock_exist, mock_mkdir): + mock_exist.return_value = False + path = os.path.join("C:/", "rss_reader") + create_path(path) + # mock_exist.assert_called_once_with(path) + mock_mkdir.assert_called() + + @patch("rss_reader.printer") + @patch("rss_reader.news_to_json") + def test_output_form(self, mock_printer, mock_news_to_json): + title = Mock() + news = Mock() + with self.assertRaises(AttributeError) as context: + output_form(title, news, "main") + self.assertTrue("Choose between console or json!" in str(context.exception)) + output_form(title, news, "json") + mock_printer.assert_called_with(title, news) + output_form(title, news, "console") + mock_news_to_json.assert_called_with(title, news) + + @patch("rss_reader.inf_generator") + def test_html_adder(self, mock_generator): + title = Mock() + data = Mock() + mock_generator.return_value = [["a", "b", "s", "d"], + ["t", "h", "s", "d"]] + self.assertIsInstance(html_adder(title, data), str) + + + +if __name__ == '__main__': + unittest.main(argv=[''], exit=False) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..eff4cd4f --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup, find_packages + +setup( + name="rss_reader", + version="1.4.0", + description="Simple RSS-reader for console use.", + author="Farrukh Nurmatov", + packages=find_packages(), + install_requires=["beautifulsoup4>=4.11.1", + "bs4>=0.0.1", + "certifi>=2022.6.15", + "charset-normalizer>=2.0.12", + "EbookLib>=0.17.1", + "idna>=3.3", + "Jinja2>=3.1.2", + "lxml>=4.9.0", + "MarkupSafe>=2.1.1", + "Pillow>=9.1.1", + "python-dateutil>=2.8.2", + "requests>=2.28.0", + "six>=1.16.0", + "soupsieve>=2.3.2.post1", + "urllib3>=1.26.9" + ], + python_requires=">=3.9", + entry_points={ + 'console_scripts': + ['rss_reader = ' + 'rss_reader.rss_reader:main', ]} + )