diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..9bebded4 Binary files /dev/null and b/.DS_Store differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/HomeTask.iml b/.idea/HomeTask.iml new file mode 100644 index 00000000..ec63674c --- /dev/null +++ b/.idea/HomeTask.iml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..dc9ea490 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..ac775c34 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Final_Task.md b/Final_Task.md new file mode 100644 index 00000000..111096e2 --- /dev/null +++ b/Final_Task.md @@ -0,0 +1,196 @@ +# Introduction to Python. Final task. +You are proposed to implement Python RSS-reader using **python 3.9**. + +The task consists of few iterations. Do not start new iteration if the previous one is not implemented yet. + +## Common requirements. +* It is mandatory to use `argparse` module. +* Codebase must be covered with unit tests with at least 50% coverage. It's a mandatory requirement. +* Yor script should **not** require installation of other services such as mysql server, +postgresql and etc. (except Iteration 6). If it does require such programs, +they should be installed automatically by your script, without user doing anything. +* In case of any mistakes utility should print human-readable. +error explanation. Exception tracebacks in stdout are prohibited in final version of application. +*Docstrings are mandatory for all methods, classes, functions and modules. +* Code must correspond to `pep8` (use `pycodestyle` utility for self-check). + * You can set line length up to 120 symbols. + +*Commit messages should provide correct and helpful information about changes in commit. Messages like `Fix bug`, +`Tried to make workable`, `Temp commit` and `Finally works` are prohibited. +*All used third-party packages should be written in the `requirements.txt` file and in installation files (`setup.py`, `setup.cfg`, etc.). +*You have to write a file with documentation. Everything must be documented: how to run scripts, how to run tests, how to install the library and etc. + +## [Iteration 1] One-shot command-line RSS reader. +RSS reader should be a command-line utility which receives [RSS](wikipedia.org/wiki/RSS) URL and prints results in human-readable format. + +You are free to choose format of the news console output. The textbox below provides an example of how it can be implemented: + +```shell +$ rss_reader.py "https://news.yahoo.com/rss/" --limit 1 + +Feed: Yahoo News - Latest News & Headlines + +Title: Nestor heads into Georgia after tornados damage Florida +Date: Sun, 20 Oct 2019 04:21:44 +0300 +Link: https://news.yahoo.com/wet-weekend-tropical-storm-warnings-131131925.html + +[image 2: Nestor heads into Georgia after tornados damage Florida][2]Nestor raced across Georgia as a post-tropical cyclone late Saturday, hours after the former tropical storm spawned a tornado that damaged +homes and a school in central Florida while sparing areas of the Florida Panhandle devastated one year earlier by Hurricane Michael. The storm made landfall Saturday on St. Vincent Island, a nature preserve +off Florida's northern Gulf Coast in a lightly populated area of the state, the National Hurricane Center said. Nestor was expected to bring 1 to 3 inches of rain to drought-stricken inland areas on its +march across a swath of the U.S. Southeast. + +Links: +[1]: https://news.yahoo.com/wet-weekend-tropical-storm-warnings-131131925.html (link) +[2]: http://l2.yimg.com/uu/api/res/1.2/Liyq2kH4HqlYHaS5BmZWpw--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en/ap.org/5ecc06358726cabef94585f99050f4f0 (image) + +``` + +Utility should provide the following interface: +```shell +usage: rss_reader.py [-h] [--version] [--json] [--verbose] [--limit LIMIT] + source + +Pure Python command-line RSS reader. + +positional arguments: + source RSS URL + +optional arguments: + -h, --help show this help message and exit + --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 + +``` + +In case of using `--json` argument your utility should convert the news into [JSON](https://en.wikipedia.org/wiki/JSON) format. +You should come up with the JSON structure on you own and describe it in the README.md file for your repository or in a separate documentation file. + + + +With the argument `--verbose` your program should print all logs in stdout. + +### Task clarification (I) + +1) If `--version` option is specified app should _just print its version_ and stop. +2) User should be able to use `--version` option without specifying RSS URL. For example: +``` +> python rss_reader.py --version +"Version 1.4" +``` +3) The version is supposed to change with every iteration. +4) If `--limit` is not specified, then user should get _all_ available feed. +5) If `--limit` is larger than feed size then user should get _all_ available news. +6) `--verbose` should print logs _in the process_ of application running, _not after everything is done_. +7) Make sure that your app **has no encoding issues** (meaning symbols like `'` and etc) when printing news to _stdout_. +8) Make sure that your app **has no encoding issues** (meaning symbols like `'` and etc) when printing news to _stdout in JSON format_. +9) It is preferrable to have different custom exceptions for different situations(If needed). +10) The `--limit` argument should also affect JSON generation. + + +## [Iteration 2] Distribution. + +* Utility should be wrapped into distribution package with `setuptools`. +* This package should export CLI utility named `rss-reader`. + + +### Task clarification (II) + +1) User should be able to run your application _both_ with and without installation of CLI utility, +meaning that this should work: + +``` +> python rss_reader.py ... +``` + +as well as this: + +``` +> rss_reader ... +``` +2) Make sure your second iteration works on a clean machie with python 3.9. (!) +3) Keep in mind that installed CLI utility should have the same functionality, so do not forget to update dependencies and packages. + + +## [Iteration 3] News caching. +The RSS news should be stored in a local storage while reading. The way and format of this storage you can choose yourself. +Please describe it in a separate section of README.md or in the documentation. + +New optional argument `--date` must be added to your utility. It should take a date in `%Y%m%d` format. +For example: `--date 20191020` +Here date means actual *publishing date* not the date when you fetched the news. + +The cashed news can be read with it. The new from the specified day will be printed out. +If the news are not found return an error. + +If the `--date` argument is not provided, the utility should work like in the previous iterations. + +### Task clarification (III) +1) Try to make your application crossplatform, meaning that it should work on both Linux and Windows. +For example when working with filesystem, try to use `os.path` lib instead of manually concatenating file paths. +2) `--date` should **not** require internet connection to fetch news from local cache. +3) User should be able to use `--date` without specifying RSS source. For example: +``` +> python rss_reader.py --date 20191206 +...... +``` +Or for second iteration (when installed using setuptools): +``` +> rss_reader --date 20191206 +...... +``` +4) If `--date` specified _together with RSS source_, then app should get news _for this date_ from local cache that _were fetched from specified source_. + +5) `--date` should work correctly with both `--json`, `--limit`, `--verbose` and their different combinations. + +## [Iteration 4] Format converter. + +You should implement the conversion of news in at least two of the suggested format: `.mobi`, `.epub`, `.fb2`, `.html`, `.pdf` + +New optional argument must be added to your utility. This argument receives the path where new file will be saved. The arguments should represents which format will be generated. + +For example: `--to-mobi` or `--to-fb2` or `--to-epub` + +You can choose yourself the way in which the news will be displayed, but the final text result should contain pictures and links, if they exist in the original article and if the format permits to store this type of data. + +### Task clarification (IV) + +Convertation options should work correctly together with all arguments that were implemented in Iterations 1-3. For example: +* Format convertation process should be influenced by `--limit`. +* If `--json` is specified together with convertation options, then JSON news should +be printed to stdout, and converted file should contain news in normal format. +* Logs from `--verbose` should be printed in stdout and not added to the resulting file. +* `--date` should also work correctly with format converter and to not require internet access. + +## * [Iteration 5] Output colorization. +> Note: An optional iteration, it is not necessary to implement it. You can move on with it only if all the previous iterations (from 1 to 4) are completely implemented. + +You should add new optional argument `--colorize`, that will print the result of the utility in colorized mode. + +*If the argument is not provided, the utility should work like in the previous iterations.* + +> Note: Take a look at the [colorize](https://pypi.org/project/colorize/) library + +## * [Iteration 6] Web-server. +> Note: An optional iteration, it is not necessary to implement it. You can move on with it only if all the previous iterations (from 1 to 4) are completely implemented. Introduction to Python course does not cover the topics that are needed for the implementation of this part. + +There are several mandatory requirements in this iteration: +* `Docker` + `docker-compose` usage (at least 2 containers: one for web-application, one for DB) +* Web application should provide all the implemented in the previous parts of the task functionality, using the REST API: + * One-shot conversion from RSS to Human readable format + * Server-side news caching + * Conversion in epub, mobi, fb2 or other formats + +Feel free to choose the way of implementation, libraries and frameworks. (We suggest you `Django Rest Framework` + `PostgreSQL` combination) + +You can implement any functionality that you want. The only requirement is to add the description into README file or update project documentation, for example: +* authorization/authentication +* automatic scheduled news update +* adding new RSS sources using API + +--- +Implementations will be checked with the latest cPython interpreter of 3.9 branch. +--- + +> Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability. **John F. Woods** diff --git a/README.md b/README.md index c86d1e65..01813e26 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,109 @@ -# How to create a PR with a homework task +RSS reader +========= -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 +This is RSS reader version 4.0. +rss_reader.py is a python script intended to get RSS feed from given source URL +and write its content to standart output also it's can convert content to .json and .html files. + + +To use this script you should install all required packages from requirements.txt +```shell +pip install {name of package} +``` + + + +How to execute after installation +------ + +Specifying the script file. Run from directory with rss_reader.py file the following command + + +Windows +```shell +python rss_reader.py ... ``` - Branch: Session_2 - DzmitryKolb - |___Task1.py - |___Task2.py - Branch: Session_3 - DzmitryKolb - |___Task1.py - |___Task2.py + +Linux +```bash + +python3 rss_reader.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` +Command line format +------- + + usage: python rss_reader.py [-h] [-v] [--json] [--verbose] [--limit LIMIT] [--date DATE] [--to-html ] + source + + Pure Python command-line RSS reader. + + positional arguments: + source RSS URL + + optional arguments: + -h, --help show this help message and exit + --version Print version info + --json Print result as JSON + --verbose Outputs verbose status messages. Prints logs. + --limit LIMIT Limit news topics. If it's not specified, then you get all available feed. + + --date DATE It should take a date in %Y%m%d format.The new from the specified day will be printed out. + --html It convert data to HTML-format in file output.html. + +РЎonsole representation +------- + +```json +Name of the feed +Title from news +PubDate + +Summary description + +Source link + +------------ +Title from news +PubDate +... +..... + + +``` +JSON representation +------- + +```json +{ + "name": Name of the feed, + "size": Number of available news, + "title": [Names of available news], + "pubDate": [Dates of publication news], + "description": [Summary description], + "link": [Link of source] +} +``` + +Cache storage format +------ + +News cache is stored in file data.json in current working directory. + + +Command example +------ + + +```shell +python rss_reader.py "https://www.onliner.by/feed" --limit 1 --html + +python rss_reader.py "https://www.buzzfeed.com/quizzes.xml" --limit 2 --json --verbose + +python rss_reader.py "https://www.buzzfeed.com/quizzes.xml" --limit 3 + +python rss_reader.py "https://feeds.fireside.fm/bibleinayear/rss" --limit 3 --verbose +python rss_reader.py --date 20220620 +``` \ No newline at end of file diff --git a/RULES.md b/RULES.md new file mode 100644 index 00000000..9a72034f --- /dev/null +++ b/RULES.md @@ -0,0 +1,12 @@ +# Final task +Final task (`FT`) for EPAM Python Training 2022.03 + +## Rules +* All work has to be implemented in the `master` branch in forked repository. If you think that `FT` is ready, please open a pull request (`PR`) to our repo. +* When a `PR` will be ready, please mark it with the `final_task` label. +* You have one month to finish `FT`. Commits commited after deadline will be ignored. +* At least the first 4 iterations must be done. +* `FT` you can find in the `Final_Task.md` file. + +### Good luck! + diff --git a/converter.py b/converter.py new file mode 100644 index 00000000..8ce589dd --- /dev/null +++ b/converter.py @@ -0,0 +1,49 @@ +from loguru import logger +import json +from json2html import * + + +class Converter: + """ + Convert from: object to dict + json to dict + dict to json + json to HTML + + """ + + def __init__(self, my_reader=None) -> None: + self.my_reader = my_reader + + def to_dict(self) -> dict: + logger.debug("Convert data to dictionary (debug)!") + dict = {"name": self.my_reader.name, + "size": self.my_reader.limit, + "title": self.my_reader.title, + "pubDate": self.my_reader.pubDate, + "description": self.my_reader.clear_description, + "link": self.my_reader.link} + return dict + + def from_json(self) -> dict: + logger.debug("Read data from json(debug)!") + with open('data.json') as json_file: + data = json.load(json_file) + return data + + def to_JSON(self, dict=False) -> None: + if not dict: + dict = self.to_dict() + logger.debug("Convert data to json (debug)!") + with open('data.json', 'w+') as outfile: + json.dump(dict, outfile, indent=4) + + def to_HTML(self): + with open("data.json") as f: + d = json.load(f) + scanOutput = json2html.convert(json=d) + htmlReportFile = "output.html" + with open(htmlReportFile, 'w') as htmlfile: + htmlfile.write(str(scanOutput)) + print("Data save in output.html") + return True diff --git a/data.json b/data.json new file mode 100644 index 00000000..f1067a87 --- /dev/null +++ b/data.json @@ -0,0 +1,16 @@ +{ + "name": "Onliner", + "size": 1, + "title": [ + "\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u044c \u0441 1 \u0438\u044e\u043b\u044f \u0432\u0432\u043e\u0434\u0438\u0442 \u0431\u0435\u0437\u0432\u0438\u0437\u043e\u0432\u044b\u0439 \u0432\u044a\u0435\u0437\u0434 \u0434\u043b\u044f \u0433\u0440\u0430\u0436\u0434\u0430\u043d \u041f\u043e\u043b\u044c\u0448\u0438" + ], + "pubDate": [ + "Thu, 30 Jun 2022 19:26:28 +0300" + ], + "description": [ + "\u0421 1 \u0438\u044e\u043b\u044f \u043f\u043e 31 \u0434\u0435\u043a\u0430\u0431\u0440\u044f \u0411\u0435\u043b\u0430\u0440\u0443\u0441\u044c \u0432\u0432\u043e\u0434\u0438\u0442 \u0431\u0435\u0437\u0432\u0438\u0437\u043e\u0432\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u0434\u043b\u044f \u0433\u0440\u0430\u0436\u0434\u0430\u043d \u041f\u043e\u043b\u044c\u0448\u0438. \u041e\u0431 \u044d\u0442\u043e\u043c \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0441\u044f \u0432\u00a0\u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c-\u043a\u0430\u043d\u0430\u043b\u0435 \u041f\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u0438\u0442\u0435\u0442\u0430.\u0427\u0438\u0442\u0430\u0442\u044c \u0434\u0430\u043b\u0435\u0435\u2026" + ], + "link": [ + "https://people.onliner.by/2022/06/30/belarus-s-1-iyulya-vvodit-bezvizovyj-vezd-dlya-grazhdan-polshi" + ] +} \ No newline at end of file diff --git a/debug.log b/debug.log new file mode 100644 index 00000000..6ac67165 --- /dev/null +++ b/debug.log @@ -0,0 +1,256 @@ +2022-06-30T03:02:18.187727+0300 DEBUG Convert to json call (debug)! +2022-06-30T03:02:18.191716+0300 DEBUG Read data from json(debug)! +2022-06-30T03:02:18.199064+0300 INFO Invalid date.12! +2022-06-30T03:11:13.840117+0300 DEBUG Convert to json call (debug)! +2022-06-30T03:11:13.842375+0300 DEBUG Read data from json(debug)! +2022-06-30T03:11:13.844956+0300 DEBUG Start data searching (debug)! +2022-06-30T03:11:13.845957+0300 DEBUG Convert data to json (debug)! +2022-06-30T03:11:13.848139+0300 INFO Succesful data search (info)! +2022-06-30T03:11:13.849176+0300 INFO Save to json file (info)! +2022-06-30T03:22:37.532486+0300 DEBUG Reader call (debug)! +2022-06-30T03:22:37.540589+0300 DEBUG Get access (debug)! +2022-06-30T03:22:38.514206+0300 DEBUG Get access (debug)! +2022-06-30T03:22:38.844512+0300 INFO Acces is available (info)! +2022-06-30T03:22:38.850225+0300 DEBUG Get title from xml (debug)! +2022-06-30T03:22:38.855341+0300 INFO Title is available (info)! +2022-06-30T03:22:38.867370+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T03:22:38.872953+0300 INFO PubDate is available (info)! +2022-06-30T03:22:38.880131+0300 DEBUG Get link from xml (debug)! +2022-06-30T03:22:38.886188+0300 INFO Link is available (info)! +2022-06-30T03:22:38.894052+0300 DEBUG Get description from xml (debug)! +2022-06-30T03:22:38.900805+0300 INFO Description is available (info)! +2022-06-30T03:22:38.906346+0300 DEBUG Convert to json call (debug)! +2022-06-30T03:22:38.919651+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T03:22:38.932401+0300 DEBUG Convert data to json (debug)! +2022-06-30T03:22:38.940350+0300 INFO Save to json file (info)! +2022-06-30T03:22:38.951201+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T03:22:38.958168+0300 DEBUG Convert data to json (debug)! +2022-06-30T03:22:38.967175+0300 INFO Save to json file (info)! +2022-06-30T03:22:38.972942+0300 DEBUG Printer call (debug)! +2022-06-30T03:22:38.980110+0300 DEBUG Read data from json(debug)! +2022-06-30T03:22:38.986679+0300 DEBUG Data is available (debug)! +2022-06-30T03:22:38.993616+0300 INFO Print information (info)! +2022-06-30T03:22:39.002501+0300 DEBUG Json-style call (debug)! +2022-06-30T03:22:39.016814+0300 DEBUG Read data from json(debug)! +2022-06-30T03:22:39.024253+0300 INFO Print json-style information (info)! +2022-06-30T03:23:17.045754+0300 DEBUG Version call (debug)! +2022-06-30T03:23:17.051827+0300 INFO Print version (info)! +2022-06-30T03:23:17.057973+0300 DEBUG Json-style call (debug)! +2022-06-30T03:23:17.067590+0300 ERROR Unexpected error (error)! +2022-06-30T03:23:52.100284+0300 DEBUG Version call (debug)! +2022-06-30T03:23:52.103679+0300 INFO Print version (info)! +2022-06-30T14:11:41.309447+0300 DEBUG Reader call (debug)! +2022-06-30T14:11:41.314282+0300 DEBUG Get access (debug)! +2022-06-30T14:11:42.239843+0300 DEBUG Get access (debug)! +2022-06-30T14:11:42.503458+0300 INFO Acces is available (info)! +2022-06-30T14:11:42.505613+0300 DEBUG Get title from xml (debug)! +2022-06-30T14:11:42.507447+0300 INFO Title is available (info)! +2022-06-30T14:11:42.508444+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T14:11:42.510439+0300 INFO PubDate is available (info)! +2022-06-30T14:11:42.511435+0300 DEBUG Get link from xml (debug)! +2022-06-30T14:11:42.513430+0300 INFO Link is available (info)! +2022-06-30T14:11:42.515555+0300 DEBUG Get description from xml (debug)! +2022-06-30T14:11:42.517421+0300 INFO Description is available (info)! +2022-06-30T14:11:42.519415+0300 DEBUG Convert to json call (debug)! +2022-06-30T14:11:42.520412+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:11:42.522407+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:11:42.526397+0300 INFO Save to json file (info)! +2022-06-30T14:11:42.529388+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:11:42.531383+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:11:42.582247+0300 INFO Save to json file (info)! +2022-06-30T14:11:42.586747+0300 DEBUG Printer call (debug)! +2022-06-30T14:11:42.588748+0300 DEBUG Read data from json(debug)! +2022-06-30T14:11:42.590743+0300 DEBUG Data is available (debug)! +2022-06-30T14:11:42.592737+0300 INFO Print information (info)! +2022-06-30T14:11:42.602713+0300 DEBUG Json-style call (debug)! +2022-06-30T14:11:42.610691+0300 DEBUG Read data from json(debug)! +2022-06-30T14:11:42.613745+0300 INFO Print json-style information (info)! +2022-06-30T14:11:42.616674+0300 ERROR Unexpected error (error)! +2022-06-30T14:14:16.206220+0300 DEBUG Reader call (debug)! +2022-06-30T14:14:16.209181+0300 DEBUG Get access (debug)! +2022-06-30T14:14:17.054766+0300 DEBUG Get access (debug)! +2022-06-30T14:14:17.244215+0300 INFO Acces is available (info)! +2022-06-30T14:14:17.247684+0300 DEBUG Get title from xml (debug)! +2022-06-30T14:14:17.248681+0300 INFO Title is available (info)! +2022-06-30T14:14:17.249951+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T14:14:17.251672+0300 INFO PubDate is available (info)! +2022-06-30T14:14:17.252741+0300 DEBUG Get link from xml (debug)! +2022-06-30T14:14:17.254667+0300 INFO Link is available (info)! +2022-06-30T14:14:17.255662+0300 DEBUG Get description from xml (debug)! +2022-06-30T14:14:17.256660+0300 INFO Description is available (info)! +2022-06-30T14:14:17.257658+0300 DEBUG Convert to json call (debug)! +2022-06-30T14:14:17.259830+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:14:17.260650+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:14:17.262645+0300 INFO Save to json file (info)! +2022-06-30T14:14:17.264639+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:14:17.265637+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:14:17.267632+0300 INFO Save to json file (info)! +2022-06-30T14:14:17.271620+0300 DEBUG Printer call (debug)! +2022-06-30T14:14:17.274614+0300 DEBUG Read data from json(debug)! +2022-06-30T14:14:17.279602+0300 DEBUG Data is available (debug)! +2022-06-30T14:14:17.283590+0300 INFO Print information (info)! +2022-06-30T14:14:17.288577+0300 DEBUG Json-style call (debug)! +2022-06-30T14:14:17.290571+0300 DEBUG Read data from json(debug)! +2022-06-30T14:14:17.292765+0300 INFO Print json-style information (info)! +2022-06-30T14:14:17.294746+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:14:17.296209+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:14:17.298952+0300 ERROR Unexpected error (error)! +2022-06-30T14:14:35.037139+0300 DEBUG Reader call (debug)! +2022-06-30T14:14:35.043143+0300 DEBUG Get access (debug)! +2022-06-30T14:14:35.233215+0300 DEBUG Get access (debug)! +2022-06-30T14:14:35.437977+0300 INFO Acces is available (info)! +2022-06-30T14:14:35.439971+0300 DEBUG Get title from xml (debug)! +2022-06-30T14:14:35.441966+0300 INFO Title is available (info)! +2022-06-30T14:14:35.444770+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T14:14:35.446481+0300 INFO PubDate is available (info)! +2022-06-30T14:14:35.448476+0300 DEBUG Get link from xml (debug)! +2022-06-30T14:14:35.449474+0300 INFO Link is available (info)! +2022-06-30T14:14:35.451717+0300 DEBUG Get description from xml (debug)! +2022-06-30T14:14:35.453464+0300 INFO Description is available (info)! +2022-06-30T14:14:35.455457+0300 DEBUG Convert to json call (debug)! +2022-06-30T14:14:35.460445+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:14:35.461441+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:14:35.464555+0300 INFO Save to json file (info)! +2022-06-30T14:14:35.466430+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:14:35.468425+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:14:35.471416+0300 INFO Save to json file (info)! +2022-06-30T14:14:35.472415+0300 DEBUG Printer call (debug)! +2022-06-30T14:14:35.478396+0300 DEBUG Read data from json(debug)! +2022-06-30T14:14:35.485633+0300 DEBUG Data is available (debug)! +2022-06-30T14:14:35.487534+0300 INFO Print information (info)! +2022-06-30T14:14:35.494355+0300 DEBUG Json-style call (debug)! +2022-06-30T14:14:35.496648+0300 DEBUG Read data from json(debug)! +2022-06-30T14:14:35.498344+0300 INFO Print json-style information (info)! +2022-06-30T14:26:40.306919+0300 DEBUG Reader call (debug)! +2022-06-30T14:26:40.312903+0300 DEBUG Get access (debug)! +2022-06-30T14:26:40.598959+0300 DEBUG Get access (debug)! +2022-06-30T14:26:40.771652+0300 INFO Acces is available (info)! +2022-06-30T14:26:40.773647+0300 DEBUG Get title from xml (debug)! +2022-06-30T14:26:40.774647+0300 INFO Title is available (info)! +2022-06-30T14:26:40.775644+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T14:26:40.777638+0300 INFO PubDate is available (info)! +2022-06-30T14:26:40.778637+0300 DEBUG Get link from xml (debug)! +2022-06-30T14:26:40.779632+0300 INFO Link is available (info)! +2022-06-30T14:26:40.781979+0300 DEBUG Get description from xml (debug)! +2022-06-30T14:26:40.783622+0300 INFO Description is available (info)! +2022-06-30T14:26:40.785616+0300 DEBUG Convert to json call (debug)! +2022-06-30T14:26:40.786614+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:26:40.787613+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:26:40.789607+0300 INFO Save to json file (info)! +2022-06-30T14:26:40.791603+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:26:40.792596+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:26:40.794665+0300 INFO Save to json file (info)! +2022-06-30T14:26:40.795691+0300 DEBUG Printer call (debug)! +2022-06-30T14:26:40.796587+0300 DEBUG Read data from json(debug)! +2022-06-30T14:26:40.798583+0300 DEBUG Data is available (debug)! +2022-06-30T14:26:40.800577+0300 INFO Print information (info)! +2022-06-30T14:26:40.802663+0300 DEBUG Json-style call (debug)! +2022-06-30T14:26:40.803597+0300 DEBUG Read data from json(debug)! +2022-06-30T14:26:40.805591+0300 INFO Print json-style information (info)! +2022-06-30T14:26:46.083582+0300 DEBUG Reader call (debug)! +2022-06-30T14:26:46.090564+0300 DEBUG Get access (debug)! +2022-06-30T14:26:46.289349+0300 DEBUG Get access (debug)! +2022-06-30T14:26:46.509778+0300 INFO Acces is available (info)! +2022-06-30T14:26:46.511772+0300 DEBUG Get title from xml (debug)! +2022-06-30T14:26:46.512770+0300 INFO Title is available (info)! +2022-06-30T14:26:46.513767+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T14:26:46.515762+0300 INFO PubDate is available (info)! +2022-06-30T14:26:46.516759+0300 DEBUG Get link from xml (debug)! +2022-06-30T14:26:46.518754+0300 INFO Link is available (info)! +2022-06-30T14:26:46.519752+0300 DEBUG Get description from xml (debug)! +2022-06-30T14:26:46.522744+0300 INFO Description is available (info)! +2022-06-30T14:26:46.524809+0300 DEBUG Convert to json call (debug)! +2022-06-30T14:26:46.526733+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:26:46.527730+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:26:46.529725+0300 INFO Save to json file (info)! +2022-06-30T14:26:46.531719+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:26:46.533714+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:26:46.535709+0300 INFO Save to json file (info)! +2022-06-30T14:26:46.538000+0300 DEBUG Printer call (debug)! +2022-06-30T14:26:46.540695+0300 DEBUG Read data from json(debug)! +2022-06-30T14:26:46.545682+0300 DEBUG Data is available (debug)! +2022-06-30T14:26:46.548021+0300 INFO Print information (info)! +2022-06-30T14:26:46.550668+0300 DEBUG Json-style call (debug)! +2022-06-30T14:26:46.555657+0300 DEBUG Read data from json(debug)! +2022-06-30T14:26:46.563271+0300 INFO Print json-style information (info)! +2022-06-30T14:26:46.566626+0300 ERROR Error with PDF-file(error)! +2022-06-30T14:29:22.976074+0300 DEBUG Reader call (debug)! +2022-06-30T14:29:22.980788+0300 DEBUG Get access (debug)! +2022-06-30T14:29:23.209324+0300 DEBUG Get access (debug)! +2022-06-30T14:29:23.393193+0300 INFO Acces is available (info)! +2022-06-30T14:29:23.395184+0300 DEBUG Get title from xml (debug)! +2022-06-30T14:29:23.397326+0300 INFO Title is available (info)! +2022-06-30T14:29:23.398176+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T14:29:23.400171+0300 INFO PubDate is available (info)! +2022-06-30T14:29:23.401168+0300 DEBUG Get link from xml (debug)! +2022-06-30T14:29:23.409820+0300 INFO Link is available (info)! +2022-06-30T14:29:23.410824+0300 DEBUG Get description from xml (debug)! +2022-06-30T14:29:23.412819+0300 INFO Description is available (info)! +2022-06-30T14:29:23.413815+0300 DEBUG Convert to json call (debug)! +2022-06-30T14:29:23.414906+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:29:23.415809+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:29:23.417805+0300 INFO Save to json file (info)! +2022-06-30T14:29:23.419798+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T14:29:23.420798+0300 DEBUG Convert data to json (debug)! +2022-06-30T14:29:23.423132+0300 INFO Save to json file (info)! +2022-06-30T14:29:23.423790+0300 DEBUG Printer call (debug)! +2022-06-30T14:29:23.424785+0300 DEBUG Read data from json(debug)! +2022-06-30T14:29:23.426781+0300 DEBUG Data is available (debug)! +2022-06-30T14:29:23.428776+0300 INFO Print information (info)! +2022-06-30T14:29:23.430771+0300 DEBUG Json-style call (debug)! +2022-06-30T14:29:23.431768+0300 DEBUG Read data from json(debug)! +2022-06-30T14:29:23.433764+0300 INFO Print json-style information (info)! +2022-06-30T14:29:23.435756+0300 ERROR Error with HTML-file(error)! +2022-06-30T15:50:56.864827+0300 DEBUG Reader call (debug)! +2022-06-30T15:50:56.870964+0300 DEBUG Get access (debug)! +2022-06-30T15:50:57.224111+0300 DEBUG Get access (debug)! +2022-06-30T15:50:57.411757+0300 INFO Acces is available (info)! +2022-06-30T15:50:57.413751+0300 DEBUG Get title from xml (debug)! +2022-06-30T15:50:57.415746+0300 INFO Title is available (info)! +2022-06-30T15:50:57.417741+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T15:50:57.418736+0300 INFO PubDate is available (info)! +2022-06-30T15:50:57.419733+0300 DEBUG Get link from xml (debug)! +2022-06-30T15:50:57.420732+0300 INFO Link is available (info)! +2022-06-30T15:50:57.422725+0300 DEBUG Get description from xml (debug)! +2022-06-30T15:50:57.423727+0300 INFO Description is available (info)! +2022-06-30T15:50:57.425722+0300 DEBUG Convert to json call (debug)! +2022-06-30T15:50:57.426717+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T15:50:57.428710+0300 DEBUG Convert data to json (debug)! +2022-06-30T15:50:57.438834+0300 INFO Save to json file (info)! +2022-06-30T15:50:57.441825+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T15:50:57.444024+0300 DEBUG Convert data to json (debug)! +2022-06-30T15:50:57.448809+0300 INFO Save to json file (info)! +2022-06-30T15:50:57.450802+0300 DEBUG Printer call (debug)! +2022-06-30T15:50:57.452796+0300 DEBUG Read data from json(debug)! +2022-06-30T15:50:57.454908+0300 DEBUG Data is available (debug)! +2022-06-30T15:50:57.458780+0300 INFO Print information (info)! +2022-06-30T15:50:57.461771+0300 DEBUG Json-style call (debug)! +2022-06-30T15:50:57.467756+0300 DEBUG Read data from json(debug)! +2022-06-30T15:50:57.471744+0300 INFO Print json-style information (info)! +2022-06-30T16:14:35.944886+0300 DEBUG Reader call (debug)! +2022-06-30T16:14:35.950869+0300 DEBUG Get access (debug)! +2022-06-30T16:14:36.249776+0300 DEBUG Get access (debug)! +2022-06-30T16:14:36.421162+0300 INFO Acces is available (info)! +2022-06-30T16:14:36.423224+0300 DEBUG Get title from xml (debug)! +2022-06-30T16:14:36.424285+0300 INFO Title is available (info)! +2022-06-30T16:14:36.426150+0300 DEBUG Get pubDate from xml (debug)! +2022-06-30T16:14:36.427145+0300 INFO PubDate is available (info)! +2022-06-30T16:14:36.428143+0300 DEBUG Get link from xml (debug)! +2022-06-30T16:14:36.430137+0300 INFO Link is available (info)! +2022-06-30T16:14:36.431136+0300 DEBUG Get description from xml (debug)! +2022-06-30T16:14:36.433130+0300 INFO Description is available (info)! +2022-06-30T16:14:36.434128+0300 DEBUG Convert to json call (debug)! +2022-06-30T16:14:36.436121+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T16:14:36.437119+0300 DEBUG Convert data to json (debug)! +2022-06-30T16:14:36.439115+0300 INFO Save to json file (info)! +2022-06-30T16:14:36.441418+0300 DEBUG Convert data to dictionary (debug)! +2022-06-30T16:14:36.443104+0300 DEBUG Convert data to json (debug)! +2022-06-30T16:14:36.445332+0300 INFO Save to json file (info)! +2022-06-30T16:14:36.446097+0300 DEBUG Printer call (debug)! +2022-06-30T16:14:36.448090+0300 DEBUG Read data from json(debug)! +2022-06-30T16:14:36.449086+0300 DEBUG Data is available (debug)! +2022-06-30T16:14:36.452079+0300 INFO Print information (info)! +2022-06-30T16:14:36.457182+0300 DEBUG Json-style call (debug)! +2022-06-30T16:14:36.463050+0300 DEBUG Read data from json(debug)! +2022-06-30T16:14:36.470032+0300 INFO Print json-style information (info)! +2022-06-30T16:14:36.474020+0300 ERROR Error with HTML-file(error)! diff --git a/output.html b/output.html new file mode 100644 index 00000000..0d218399 --- /dev/null +++ b/output.html @@ -0,0 +1 @@ +
nameOnliner
size1
title
  • Беларусь с 1 июля вводит безвизовый въезд для граждан Польши
pubDate
  • Thu, 30 Jun 2022 19:26:28 +0300
description
  • С 1 июля по 31 декабря Беларусь вводит безвизовый режим для граждан Польши. Об этом говорится в телеграм-канале Пограничного комитета.Читать далее…
link
  • https://people.onliner.by/2022/06/30/belarus-s-1-iyulya-vvodit-bezvizovyj-vezd-dlya-grazhdan-polshi
\ No newline at end of file diff --git a/printer.py b/printer.py new file mode 100644 index 00000000..7a38ec58 --- /dev/null +++ b/printer.py @@ -0,0 +1,16 @@ +from loguru import logger + + +class Printer: + """Print result in stdout""" + + def __init__(self, data: dict) -> None: + logger.debug("Data is available (debug)!") + self.data = data + + def __str__(self) -> str: + return self.data["name"] + '\n' + ''.join([(f'{self.data["title"][i]}\n' + f'{self.data["pubDate"][i]}\n\n' + f'{self.data["description"][i]}\n\n' + f'{self.data["link"][i]}\n\n---------------\n\n') for i in + range(self.data["size"])]) diff --git a/reader.py b/reader.py new file mode 100644 index 00000000..fb61345a --- /dev/null +++ b/reader.py @@ -0,0 +1,71 @@ +from loguru import logger +import requests +from bs4 import BeautifulSoup +import re +import sys + + +class Reader: + """Parse data from URL""" + + def __init__(self, source: str, limit=-1) -> None: + self.version = '4.0' + self.source = source + self.name = self.get_acces()[0] + self.items = self.get_acces()[1] + logger.info("Acces is available (info)!") + self.limit = len(self.items) if limit == -1 or limit > len(self.items) else limit + self.title = self.get_title() + logger.info("Title is available (info)!") + self.pubDate = self.get_pubDate() + logger.info("PubDate is available (info)!") + self.link = self.get_link() + logger.info("Link is available (info)!") + self.clear_description = list() + self.description = self.get_description() + logger.info("Description is available (info)!") + + def get_acces(self) -> list: + logger.debug("Get access (debug)!") + try: + url = requests.get(self.source) + except Exception: + logger.info(f"Invalid url.{self.source}(info)!") + print('Could not fetch the URL. Input valid URL.') + sys.exit() + try: + soup = BeautifulSoup(url.content, 'xml') + name = soup.find().title.text + items = soup.find_all('item') + if len(items) == 0: + raise Exception + except Exception as e: + logger.info(f"Invalid url.{self.source}(info)!") + print('Could not read feed. Input xml-format URL.') + sys.exit() + return name, items + + def get_title(self) -> list: + logger.debug("Get title from xml (debug)!") + return [self.items[i].title.text for i in range(self.limit)] + + def get_pubDate(self) -> list: + logger.debug("Get pubDate from xml (debug)!") + print([self.items[i].pubDate.text for i in range(self.limit)]) + return [self.items[i].pubDate.text for i in range(self.limit)] + + def get_link(self) -> list: + logger.debug("Get link from xml (debug)!") + return [self.items[i].link.text for i in range(self.limit)] + + def get_description(self) -> list: + logger.debug("Get description from xml (debug)!") + des = [] + for i in range(self.limit): + if self.items[i].description: + des.append(self.items[i].description.text) + self.clear_description.append(re.sub(r'\<[^>]*\>|(&rsaquo)', '', self.items[i].description.text)) + else: + des.append('No description here') + self.clear_description.append('No description here') + return des diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..055ff375 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +beautifulsoup4==4.11.1 +charset-normalizer==2.0.12 +idna==3.3 +lxml==4.9.0 +soupsieve==2.3.2.post1 +urllib3==1.26.9 +python-dateutil==2.8.2 +reportlab==3.6.10 +json2html==1.3.0 +requests==2.27.1 \ No newline at end of file diff --git a/rss_reader.py b/rss_reader.py new file mode 100644 index 00000000..0addbddb --- /dev/null +++ b/rss_reader.py @@ -0,0 +1,118 @@ +import argparse +import sys +from loguru import logger + +from reader import Reader +from printer import Printer +from converter import Converter + +VERSION = '4.0' + +logger.add("debug.log", format="{time} {level} {message}", level="DEBUG") + + +def args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Choose type of the interface") + parser.add_argument("-v", "--version", action="store_true", help="Print version info") + parser.add_argument("--json", action="store_true", help="Print result as JSON") + parser.add_argument("--verbose", action="store_true", help="Outputs verbose status messages.Print logs.") + parser.add_argument("--limit", type=int, + help="Limit news topics. If it's not specified, then you get all available feed") + parser.add_argument("--date", type=str, + help="It should take a date in Ymd format.The new from the specified day will be printed out.") + parser.add_argument("--html", action="store_true", help="It convert data to HTML-format in file output.html.") + parser.add_argument("source", nargs="?", type=str, help="RSS URL") + args = parser.parse_args() + return args + + +def date_search(data: dict, date: str) -> dict: + if len(date) < 7 or not (date.isdigit()): + logger.info(f"Invalid date.{date}!") + print(f"Invalid date.{date}. Use pattern YMD!") + raise sys.exit() + + logger.debug("Start data searching (debug)!") + new_dates = [] + month = {"Jan": "01", "Feb": "02", "Mar": "03", "Apr": "04", "May": "05", "Jun": "06", "Jul": "07", "Aug": "08", + "Sep": "09", "Oct": "10", "Nov": "11", "Dec": "12"} + for d in data["pubDate"]: + for key in month.keys(): + if key in d: + new_dates.append(d.replace(key, month[key])) + new_date = date[6::] + ' ' + date[4:6:] + ' ' + date[:4:] + list_of_index = [] + + for i in range(len(new_dates)): + if new_date in new_dates[i]: + list_of_index.append(i) + if len(list_of_index) == 0: + logger.info(f"No information found.{date}!") + print(f"No information found.{date}.") + raise sys.exit() + new_data = {"name": data["name"], + "size": len(list_of_index), + "title": [data["title"][i] for i in list_of_index], + "pubDate": [data["pubDate"][i] for i in list_of_index], + "description": [data["description"][i] for i in list_of_index], + "link": [data["link"][i] for i in list_of_index] + } + return new_data + + +def main(): + if not args().verbose: + logger.remove() + if args().version: + logger.debug("Version call (debug)!") + print('Version:' + VERSION) + logger.info("Print version (info)!") + sys.exit() + elif args().date and args().source == None: + converter = Converter() + logger.debug("Convert to json call (debug)!") + converter.to_JSON(date_search(converter.from_json(), args().date)) + logger.info("Succesful data search (info)!") + logger.info("Save to json file (info)!") + printer = Printer(converter.from_json()) + print(printer) + else: + logger.debug("Reader call (debug)!") + my_reader = Reader(args().source, args().limit) if args().limit else Reader(args().source) + converter = Converter(my_reader) + logger.debug("Convert to json call (debug)!") + converter.to_JSON() + logger.info("Save to json file (info)!") + if args().date: + date_search(converter.from_json(), args().date) + converter.to_JSON(date_search(converter.from_json(), args().date)) + logger.info("Succesful data search (info)!") + else: + converter.to_JSON() + logger.info("Save to json file (info)!") + logger.debug("Printer call (debug)!") + printer = Printer(converter.from_json()) + print(printer) + logger.info("Print information (info)!") + + if args().json: + logger.debug("Json-style call (debug)!") + print("Json style:\n") + print(converter.from_json()) + logger.info("Print json-style information (info)!") + + if args().html: + try: + converter.to_HTML() + except Exception: + logger.error(f"Error with HTML-file(error)!") + print('Error: convert to HTML-file does not work with this URL ') + + +if __name__ == '__main__': + try: + main() + except Exception: + logger.error(f"Unexpected error (error)!") + print(f"Unexpected error") + raise sys.exit() diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..51d89817 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +import os +from setuptools import find_packages, setup + + +def read(file_name): + with open(os.path.join(os.path.dirname(__file__), file_name)) as file: + return file.read() +setup( + name="rss_reader", + version="4.0", + author="Maya Voyshnis", + author_email="vvvoyshnism@gmail.com", + description='Python RSS Parser', + # long_description=open('README.txt').read(), + packages=find_packages(), + install_requires=[ + "wheel", + "setuptools", + "argparse", + "requests", + "beautifulsoup4", + "python-dateutil", + "loguru" + ], + entry_points={ + 'console_scripts': [ + 'rss_reader=rss_reader.main:main' + ], + } +) diff --git a/test.py b/test.py new file mode 100644 index 00000000..c1b03022 --- /dev/null +++ b/test.py @@ -0,0 +1,62 @@ +import unittest + +from reader import Reader +from converter import Converter +from printer import Printer + + +class TestReader(unittest.TestCase): + + def setUp(self): + self.reader1 = Reader("https://www.onliner.by/feed", 1) + self.reader2 = Reader("https://www.onliner.by/feed", 2) + + def test_get_acces(self): + self.assertEqual(type(self.reader1.get_title()), list) + + def test_get_title(self): + self.assertEqual(len(self.reader1.get_title()), 1) + + def test_get_pubDate(self): + self.assertEqual(len(self.reader2.get_pubDate()), 2) + + def test_get_link(self): + self.assertEqual(len(self.reader2.get_pubDate()), 2) + + def test_get_descriprion(self): + self.assertEqual(len(self.reader1.get_pubDate()), 1) + + def test_get_acces(self): + self.assertEqual(type(self.reader1.get_title()), list) + + +class TestConverter(unittest.TestCase): + def setUp(self): + self.converter1 = Converter(Reader("https://feeds.fireside.fm/bibleinayear/rss", 3)) + self.converter2 = Converter() + + def test_to_dict(self): + self.assertEqual(len(self.converter1.to_dict()), 6) + + def test_from_json(self): + self.assertEqual(len(self.converter1.from_json()), 6) + + def test_from_json2(self): + self.assertEqual(type(self.converter2.from_json()), dict) + + def test_to_HTML(self): + self.assertEqual(self.converter1.to_HTML(), True) + + +class TestPrinter(unittest.TestCase): + def setUp(self): + self.converter1 = Converter(Reader("https://feeds.fireside.fm/bibleinayear/rss", 3)) + self.printer = Printer(self.converter1.from_json()) + + def test_print(self): + print(self.printer) + + +if __name__ == "__main__": + unittest.main() +