From 28ea5c43d62bee6b41a71259288d0c998736b7fe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 10 Jun 2022 21:33:59 +0300 Subject: [PATCH 001/973] init oza-rss-reader --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..600d2d33 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file From 0fdec856bdfbeec5f7f1c3108aa99082ea1e9bba Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 09:58:41 +0300 Subject: [PATCH 002/973] add .idea to .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 600d2d33..4f1a202c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.vscode \ No newline at end of file +.vscode +.idea +__pycache__ \ No newline at end of file From 9cc1b31c334085d809e28c4491de7c8609ee294a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:08:05 +0300 Subject: [PATCH 003/973] add __init__ to rss-reader --- rss-reader/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss-reader/__init__.py diff --git a/rss-reader/__init__.py b/rss-reader/__init__.py new file mode 100644 index 00000000..e69de29b From 2df527e22033827cb0ffa8000c32dafc3f3591bb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:22:43 +0300 Subject: [PATCH 004/973] add __main__ to rss-reader --- rss-reader/__main__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss-reader/__main__.py diff --git a/rss-reader/__main__.py b/rss-reader/__main__.py new file mode 100644 index 00000000..e69de29b From ff033b44d64f9ad62d67c259084118ec28385410 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:34:21 +0300 Subject: [PATCH 005/973] add __init__ to starter --- rss-reader/starter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss-reader/starter/__init__.py diff --git a/rss-reader/starter/__init__.py b/rss-reader/starter/__init__.py new file mode 100644 index 00000000..e69de29b From 2aba707a2f4419a0e43b317090f9770be06acf41 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:36:23 +0300 Subject: [PATCH 006/973] rename from rss-reader to rss_reader --- {rss-reader => rss_reader}/__init__.py | 0 {rss-reader => rss_reader}/__main__.py | 0 {rss-reader => rss_reader}/starter/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {rss-reader => rss_reader}/__init__.py (100%) rename {rss-reader => rss_reader}/__main__.py (100%) rename {rss-reader => rss_reader}/starter/__init__.py (100%) diff --git a/rss-reader/__init__.py b/rss_reader/__init__.py similarity index 100% rename from rss-reader/__init__.py rename to rss_reader/__init__.py diff --git a/rss-reader/__main__.py b/rss_reader/__main__.py similarity index 100% rename from rss-reader/__main__.py rename to rss_reader/__main__.py diff --git a/rss-reader/starter/__init__.py b/rss_reader/starter/__init__.py similarity index 100% rename from rss-reader/starter/__init__.py rename to rss_reader/starter/__init__.py From dae0871856bf882c3fb012b16048719406bb5e95 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:41:13 +0300 Subject: [PATCH 007/973] add base.py --- rss_reader/starter/base.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/starter/base.py diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py new file mode 100644 index 00000000..e69de29b From aaa58ea38c7db2592085b97484467c5a3648b518 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:43:10 +0300 Subject: [PATCH 008/973] add NAME_LOGGER --- rss_reader/starter/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index e69de29b..a6d91d01 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -0,0 +1,3 @@ + + +NAME_LOGGER = 'rss-reader' From f113448c34ae6b9776e87f866377469a736c13e8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:44:50 +0300 Subject: [PATCH 009/973] add version --- rss_reader/starter/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index a6d91d01..212f3c37 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -1,3 +1,4 @@ NAME_LOGGER = 'rss-reader' +version = '0.0.1' From f23d2eac080dae3d600c9c3a4b667783fb9f3524 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:46:47 +0300 Subject: [PATCH 010/973] add init_arguments_functionality function --- rss_reader/starter/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 212f3c37..48916d96 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -2,3 +2,7 @@ NAME_LOGGER = 'rss-reader' version = '0.0.1' + + +def init_arguments_functionality(args=None) -> Dict[str, str]: + pass From 9f61e80fa858de67e2834499212f66857ac09dff Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 10:47:29 +0300 Subject: [PATCH 011/973] add import Dict --- rss_reader/starter/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 48916d96..9cef2a57 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -1,5 +1,8 @@ +from typing import Dict + + NAME_LOGGER = 'rss-reader' version = '0.0.1' From e3a9642c06d6a35f97f2ce68dc52be647a481b8c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:03:56 +0300 Subject: [PATCH 012/973] add docstring --- rss_reader/starter/base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 9cef2a57..caac937b 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -8,4 +8,10 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: - pass + """Create command line options. + + :param args: Command line options, defaults to None. + :type args: List, optional. + :return: Command line parameter dictionary. + :rtype: Dict[str, str] + """ From ee7a8d10f43b26e560300829d6a02ec4b3389486 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:17:26 +0300 Subject: [PATCH 013/973] import argparse --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index caac937b..8fdc8698 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -1,5 +1,5 @@ - +import argparse from typing import Dict From d5d1ba298a0c2d0670f74a9922545a37d150764d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:20:50 +0300 Subject: [PATCH 014/973] add ArgumentParser --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 8fdc8698..3c9cc516 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -15,3 +15,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: :return: Command line parameter dictionary. :rtype: Dict[str, str] """ + + parser = argparse.ArgumentParser() From 6ff989714389814d107a974863b1703680b513b1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:21:34 +0300 Subject: [PATCH 015/973] add prog --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 3c9cc516..7aecacea 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -16,4 +16,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: :rtype: Dict[str, str] """ - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + prog='RSS reader',) From ec1a18b62c9c21b2a1848560f2aef85cd6099091 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:22:04 +0300 Subject: [PATCH 016/973] add description --- rss_reader/starter/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 7aecacea..797c7833 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -17,4 +17,6 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: """ parser = argparse.ArgumentParser( - prog='RSS reader',) + prog='RSS reader', + description='Pure Python command-line RSS reader.', + ) From 471368083eeec9ab6697367d64b1603a07cd5e50 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:23:21 +0300 Subject: [PATCH 017/973] add epilog --- rss_reader/starter/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 797c7833..9b5834c5 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -19,4 +19,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser = argparse.ArgumentParser( prog='RSS reader', description='Pure Python command-line RSS reader.', + epilog='''(c) 2022. Have a nice day.''' ) From 2071f547ec5007b243db80d455fed3ec23adfc00 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:26:40 +0300 Subject: [PATCH 018/973] add argument source --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 9b5834c5..3616203a 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -21,3 +21,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: description='Pure Python command-line RSS reader.', epilog='''(c) 2022. Have a nice day.''' ) + + parser.add_argument('source') From aeb9f422054010188b857d87e858c6f3eed3ae0c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:33:18 +0300 Subject: [PATCH 019/973] add nargs --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 3616203a..f7ef5eb3 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -22,4 +22,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: epilog='''(c) 2022. Have a nice day.''' ) - parser.add_argument('source') + parser.add_argument('source', + nargs='?') From ecd5bb48727d76699ad9789b4b6ce54f8a9ea496 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:33:58 +0300 Subject: [PATCH 020/973] add default --- rss_reader/starter/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index f7ef5eb3..ad2d133f 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -23,4 +23,6 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: ) parser.add_argument('source', - nargs='?') + nargs='?', + default='https://news.yahoo.com/rss/' + ) From c634c42fe52263b9be4dc53c51c73802a4d95a1a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:34:55 +0300 Subject: [PATCH 021/973] add help to source argument --- rss_reader/starter/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index ad2d133f..42f771e8 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -24,5 +24,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('source', nargs='?', - default='https://news.yahoo.com/rss/' - ) + default='https://news.yahoo.com/rss/', + help='RSS URL') From 32fdcc95a800cbe3c7a7a53d1c12dc51d9e85f70 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:47:19 +0300 Subject: [PATCH 022/973] add version argument --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 42f771e8..9962f7d2 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -26,3 +26,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: nargs='?', default='https://news.yahoo.com/rss/', help='RSS URL') + + parser.add_argument('--version') From 34532a5789f747ca1d3a5194fe88b246d3c8aec3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:47:53 +0300 Subject: [PATCH 023/973] add action to version argument --- rss_reader/starter/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 9962f7d2..0ccef345 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -26,5 +26,6 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: nargs='?', default='https://news.yahoo.com/rss/', help='RSS URL') - - parser.add_argument('--version') + + parser.add_argument('--version', + action='version') From bc1e474115ffa2f9a72fcde90bf911b3bcd60d12 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:48:48 +0300 Subject: [PATCH 024/973] add version to version argument --- rss_reader/starter/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 0ccef345..d7bef2ae 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -26,6 +26,7 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: nargs='?', default='https://news.yahoo.com/rss/', help='RSS URL') - + parser.add_argument('--version', - action='version') + action='version', + version='%(prog)s version {}'.format(version)) From 251cdd7cda994cdee74defe9ba2abb54686b6be7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:49:23 +0300 Subject: [PATCH 025/973] add help to version argument --- rss_reader/starter/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index d7bef2ae..1f8eef95 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -26,7 +26,8 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: nargs='?', default='https://news.yahoo.com/rss/', help='RSS URL') - + parser.add_argument('--version', action='version', - version='%(prog)s version {}'.format(version)) + version='%(prog)s version {}'.format(version), + help='Print version info') From 6ef240b229d9f90a1694b4aca0a834fd2478f829 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:50:29 +0300 Subject: [PATCH 026/973] add json argument --- rss_reader/starter/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 1f8eef95..ece03737 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -26,8 +26,10 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: nargs='?', default='https://news.yahoo.com/rss/', help='RSS URL') - + parser.add_argument('--version', action='version', version='%(prog)s version {}'.format(version), help='Print version info') + + parser.add_argument('--json') From 160e0bdfbaa4ff97d4a3587578ce22254676ffd6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:51:01 +0300 Subject: [PATCH 027/973] add action to json argument --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index ece03737..5a9f1a8b 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -32,4 +32,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: version='%(prog)s version {}'.format(version), help='Print version info') - parser.add_argument('--json') + parser.add_argument('--json', + action='store_true') From 2123f36d8db082e49157d5cbb8705add0e32b2fd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:51:24 +0300 Subject: [PATCH 028/973] add help to json argument --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 5a9f1a8b..5f7ab733 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -33,4 +33,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: help='Print version info') parser.add_argument('--json', - action='store_true') + action='store_true', + help='Print result as JSON in stdout') From 761be919f8f0dbd644941ad069f4d0cdf2035fc7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:52:08 +0300 Subject: [PATCH 029/973] add verbose argument --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 5f7ab733..5b86161e 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -35,3 +35,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('--json', action='store_true', help='Print result as JSON in stdout') + + parser.add_argument('--verbose') From 8e7fce5131a81dfa5333ab5b0f28b2134f585cd6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:52:30 +0300 Subject: [PATCH 030/973] add action to verbose argument --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 5b86161e..9e65225a 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -36,4 +36,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: action='store_true', help='Print result as JSON in stdout') - parser.add_argument('--verbose') + parser.add_argument('--verbose', + action='store_true') From 0b10abc9f19e3257eb741f3247aaa6301e4d0a2a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:52:45 +0300 Subject: [PATCH 031/973] add help to verbose argument --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 9e65225a..34a42245 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -37,4 +37,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: help='Print result as JSON in stdout') parser.add_argument('--verbose', - action='store_true') + action='store_true', + help='Outputs verbose status messages') From 44b3511b127c8082c5d2197910354e37be531fcf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:53:34 +0300 Subject: [PATCH 032/973] add limit argument --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 34a42245..4d9320bd 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -39,3 +39,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('--verbose', action='store_true', help='Outputs verbose status messages') + + parser.add_argument('--limit') From 2f4b0e1dcbf2f8f371573cd54a3f57b8c9282cd1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:53:50 +0300 Subject: [PATCH 033/973] add help to limit argument --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 4d9320bd..05393142 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -40,4 +40,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: action='store_true', help='Outputs verbose status messages') - parser.add_argument('--limit') + parser.add_argument('--limit', + help='Limit news topics if this parameter provided') From 50ea5218f5af5925fb7ba54ae214c9b017f9c7f7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:57:00 +0300 Subject: [PATCH 034/973] get the passed attributes object --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 05393142..07999541 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -42,3 +42,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('--limit', help='Limit news topics if this parameter provided') + + namespace_ = parser.parse_args(args) From b7427d3f844fed3e4a889f7c5b9092a93c35e2f0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 11:58:15 +0300 Subject: [PATCH 035/973] return a dictionary of passed attributes --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 07999541..f76515f7 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -44,3 +44,5 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: help='Limit news topics if this parameter provided') namespace_ = parser.parse_args(args) + + return vars(namespace_) From f75a96cb02e4fd94d1a0a9dfb1502677448fee2d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 12:52:35 +0300 Subject: [PATCH 036/973] add docstring to base module --- rss_reader/starter/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index f76515f7..3482dab9 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -1,3 +1,8 @@ +"""Initial program setup. + +This module contains functions intended for the initial setup of the program. +""" + import argparse from typing import Dict From 8ca203a9933f3ac4270154e46bf2ac70a8be48a8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 12:58:08 +0300 Subject: [PATCH 037/973] add docstring to the starter package --- rss_reader/starter/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/__init__.py b/rss_reader/starter/__init__.py index e69de29b..8cfce077 100644 --- a/rss_reader/starter/__init__.py +++ b/rss_reader/starter/__init__.py @@ -0,0 +1 @@ +"""Package for the initial setup of the program.""" From d35e081ea950d15d46d052291038f6230d7417c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:00:44 +0300 Subject: [PATCH 038/973] add docstring __main__ --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index e69de29b..38ec2b22 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -0,0 +1 @@ +"""Program entry point.""" From c42150188e12601fb6992750e423459c1123c52b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:13:09 +0300 Subject: [PATCH 039/973] add import init_arguments_functionality --- rss_reader/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 38ec2b22..3e38e5ef 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -1 +1,4 @@ """Program entry point.""" + + +from rss_reader.starter.base import init_arguments_functionality as iaf From f64dcad4a7eb270b58782e4894bdeaeae1231882 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:15:06 +0300 Subject: [PATCH 040/973] create start point entry --- rss_reader/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 3e38e5ef..2bc61220 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -2,3 +2,7 @@ from rss_reader.starter.base import init_arguments_functionality as iaf + + +if __name__ == "__main__": + pass From 137884e8beaca834c3b690a631a9f7d5ee18174b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:15:48 +0300 Subject: [PATCH 041/973] create main function --- rss_reader/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 2bc61220..5f8a802b 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -4,5 +4,8 @@ from rss_reader.starter.base import init_arguments_functionality as iaf +def main(): + pass + if __name__ == "__main__": pass From c456c9f02580843fe81467d7bdc86cc0621a6c14 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:17:15 +0300 Subject: [PATCH 042/973] call iaf function --- rss_reader/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 5f8a802b..beb17f7a 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -5,7 +5,8 @@ def main(): - pass + args = iaf() + if __name__ == "__main__": pass From 531baf4484513db0c38c1f943af8aa512cef7b9a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:18:40 +0300 Subject: [PATCH 043/973] call main function --- rss_reader/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index beb17f7a..a44a577f 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -9,4 +9,4 @@ def main(): if __name__ == "__main__": - pass + main() From 406ba4eed1bf93b6c4174efb739fc773495825a3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:32:10 +0300 Subject: [PATCH 044/973] create __init__ in logger package --- rss_reader/logger/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/logger/__init__.py diff --git a/rss_reader/logger/__init__.py b/rss_reader/logger/__init__.py new file mode 100644 index 00000000..e69de29b From e100cc2e3c3329facad72b01f39874f7ac1549e1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:34:50 +0300 Subject: [PATCH 045/973] add logger.py file --- rss_reader/logger/logger.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/logger/logger.py diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py new file mode 100644 index 00000000..e69de29b From e16be0c0227980d70dd3b6bba7acb7bae546e6b0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:35:23 +0300 Subject: [PATCH 046/973] create class Logger --- rss_reader/logger/logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index e69de29b..25815949 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -0,0 +1,4 @@ + + +class Logger: + pass From 260d753cf2c41a903d4d420888d3c0c6a9431930 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:37:00 +0300 Subject: [PATCH 047/973] add a docstring to class Logger --- rss_reader/logger/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 25815949..e70f1dde 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,4 +1,6 @@ class Logger: + """Logs data.""" + pass From f47824525feda5616087c3f0700c92617c11cdcc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:37:51 +0300 Subject: [PATCH 048/973] add NAME_LOGGER to class Logger --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index e70f1dde..7bd0812a 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -3,4 +3,4 @@ class Logger: """Logs data.""" - pass + NAME_LOGGER = 'base_logger' From 172eb37b03ceb17bfec3c4bddea4a42e70dc8385 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:39:29 +0300 Subject: [PATCH 049/973] add __init__ to class Logger --- rss_reader/logger/logger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 7bd0812a..c36c8f69 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -4,3 +4,6 @@ class Logger: """Logs data.""" NAME_LOGGER = 'base_logger' + + def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: + pass From 955029b7ba7af8a8c2ee161ee3e91891d6f512cd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:40:58 +0300 Subject: [PATCH 050/973] change class variable NAME_LOGGER --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index c36c8f69..7d4aa75d 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -6,4 +6,4 @@ class Logger: NAME_LOGGER = 'base_logger' def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: - pass + Logger.NAME_LOGGER = name_loger From cac2d4dd5e4a3815dd929cede9381fa9b6faf254 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 13:45:14 +0300 Subject: [PATCH 051/973] set the _config variable of an instance of class Logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 7d4aa75d..fd7293f1 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -7,3 +7,4 @@ class Logger: def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: Logger.NAME_LOGGER = name_loger + self._config = config From 93cae0476d6ab644d5abe6e0f111a678b0c59752 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:03:21 +0300 Subject: [PATCH 052/973] add a docstring to __init__ of the Logger --- rss_reader/logger/logger.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index fd7293f1..51c77195 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -6,5 +6,12 @@ class Logger: NAME_LOGGER = 'base_logger' def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: + """Initializer. + + :param name_loger: Logger name. + :type name_loger: str + :param config: Logger configuration. + :type config: ISetLoggerConfig + """ Logger.NAME_LOGGER = name_loger self._config = config From 0510c098d02a8f0779af7e42abbaae23eba55175 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:09:41 +0300 Subject: [PATCH 053/973] add setup_logger method to the Logger --- rss_reader/logger/logger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 51c77195..1f6f19a3 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -15,3 +15,6 @@ def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: """ Logger.NAME_LOGGER = name_loger self._config = config + + def setup_logger(self): + pass From 41cb929b29f42d2a706670189e2c892a85bd485c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:11:26 +0300 Subject: [PATCH 054/973] add get_logger method to the Logger --- rss_reader/logger/logger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 1f6f19a3..e485fc50 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -18,3 +18,6 @@ def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: def setup_logger(self): pass + + def get_logger(cls, module_name: str): + pass From eb3bd500b5b442a07a503c29d403edd74df7ab5e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:16:19 +0300 Subject: [PATCH 055/973] add __init__ to the interfaces package --- rss_reader/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/__init__.py diff --git a/rss_reader/interfaces/__init__.py b/rss_reader/interfaces/__init__.py new file mode 100644 index 00000000..e69de29b From 7632186a81893e3953865a73f76df220a2c1ca51 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:18:40 +0300 Subject: [PATCH 056/973] add a dockstring to the interfaces package --- rss_reader/interfaces/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/__init__.py b/rss_reader/interfaces/__init__.py index e69de29b..ad9530e9 100644 --- a/rss_reader/interfaces/__init__.py +++ b/rss_reader/interfaces/__init__.py @@ -0,0 +1 @@ +"""This package contains modules with interfaces.""" From 022c2245f23211e97fd8fa979dbcf04733afce4c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:20:41 +0300 Subject: [PATCH 057/973] add __init__ to the ilogger package --- rss_reader/interfaces/ilogger/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/ilogger/__init__.py diff --git a/rss_reader/interfaces/ilogger/__init__.py b/rss_reader/interfaces/ilogger/__init__.py new file mode 100644 index 00000000..e69de29b From e3d8946a05ebc03c2b6ee56101e2a4b9f00b0b78 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:22:29 +0300 Subject: [PATCH 058/973] add a dockstring to the ilogger package --- rss_reader/interfaces/ilogger/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/ilogger/__init__.py b/rss_reader/interfaces/ilogger/__init__.py index e69de29b..72821274 100644 --- a/rss_reader/interfaces/ilogger/__init__.py +++ b/rss_reader/interfaces/ilogger/__init__.py @@ -0,0 +1 @@ +"""This package contains the logger interfaces.""" \ No newline at end of file From 3f84274d0145edc2271fdf4ed752d7e2d4561aaf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:25:04 +0300 Subject: [PATCH 059/973] add a dockstring to the ilogger package --- rss_reader/interfaces/ilogger/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/ilogger/__init__.py b/rss_reader/interfaces/ilogger/__init__.py index 72821274..7b0a4021 100644 --- a/rss_reader/interfaces/ilogger/__init__.py +++ b/rss_reader/interfaces/ilogger/__init__.py @@ -1 +1 @@ -"""This package contains the logger interfaces.""" \ No newline at end of file +"""This package contains the logger interfaces.""" From c7c9f5cdd760d9cef97d955f65867b4ebb86bf3f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:26:12 +0300 Subject: [PATCH 060/973] add ilogger module --- rss_reader/interfaces/ilogger/ilogger.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/ilogger/ilogger.py diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py new file mode 100644 index 00000000..e69de29b From 07f9f3f96ec82cc1917fc887194d5a923729265b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:27:58 +0300 Subject: [PATCH 061/973] create class ISetLoggerConfig --- rss_reader/interfaces/ilogger/ilogger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index e69de29b..a5fe77a7 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -0,0 +1,4 @@ + + +class ISetLoggerConfig(ABC): + pass From 30e97d13eba6e82b8eab7c256df83b3d01975c8f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:32:12 +0300 Subject: [PATCH 062/973] create abstractmethod set_config --- rss_reader/interfaces/ilogger/ilogger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index a5fe77a7..46e5976b 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -1,4 +1,6 @@ class ISetLoggerConfig(ABC): - pass + @abstractmethod + def set_config(self, name: str): + pass From 00b856018c310cdf9abf4c11f73b0af504aca4cb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:35:40 +0300 Subject: [PATCH 063/973] import ABC --- rss_reader/interfaces/ilogger/ilogger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index 46e5976b..f2069a55 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -1,4 +1,6 @@ +from abc import ABC + class ISetLoggerConfig(ABC): @abstractmethod From 606037f2d5a27b218b8646dd267837a9240ddd2b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:36:04 +0300 Subject: [PATCH 064/973] import abstractmethod --- rss_reader/interfaces/ilogger/ilogger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index f2069a55..93332d6a 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -1,5 +1,5 @@ -from abc import ABC +from abc import ABC, abstractmethod class ISetLoggerConfig(ABC): From db838623a0a2d8d194f60490431433e79888db80 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:36:54 +0300 Subject: [PATCH 065/973] import Logger from logging --- rss_reader/interfaces/ilogger/ilogger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index 93332d6a..e0ecabcc 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod +from logging import Logger as LG class ISetLoggerConfig(ABC): From 5cd6777dcd47d94583efa3dc9dcba5a9e5b7d2c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:37:42 +0300 Subject: [PATCH 066/973] add return type --- rss_reader/interfaces/ilogger/ilogger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index e0ecabcc..7890d090 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -5,5 +5,5 @@ class ISetLoggerConfig(ABC): @abstractmethod - def set_config(self, name: str): + def set_config(self, name: str) -> LG: pass From 05d685dec46b4193550aaa1bb828f92b440a6e65 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:39:29 +0300 Subject: [PATCH 067/973] add a docstring to the set_config --- rss_reader/interfaces/ilogger/ilogger.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index 7890d090..a4f9516d 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -6,4 +6,11 @@ class ISetLoggerConfig(ABC): @abstractmethod def set_config(self, name: str) -> LG: + """Set logger configuration. + + :param name: Logger name. + :type name: str + :return: object Logger. + :rtype: LG + """ pass From 2a7ef338a9393ce87e0b97b8ab3f98e5bcf6e2b2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:40:49 +0300 Subject: [PATCH 068/973] add a docstring to the ilogger module --- rss_reader/interfaces/ilogger/ilogger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index a4f9516d..0e0103b4 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -1,3 +1,5 @@ +"""This module contains a set of interfaces for the logger.""" + from abc import ABC, abstractmethod from logging import Logger as LG From 645ad26a4fe6fa6680c48a113cedb516b945271e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:44:13 +0300 Subject: [PATCH 069/973] import ISetLoggerConfig --- rss_reader/logger/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index e485fc50..6fda8971 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,4 +1,6 @@ +from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig + class Logger: """Logs data.""" From 044482dbb1bc048fbe95cabf97107e5a9b16d7b3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:47:10 +0300 Subject: [PATCH 070/973] return logger --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 6fda8971..11012766 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -19,7 +19,7 @@ def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: self._config = config def setup_logger(self): - pass + return self._config.set_config(Logger.NAME_LOGGER) def get_logger(cls, module_name: str): pass From 204e3a1eb71fe5ade30169fe292ff88781eb593e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:48:50 +0300 Subject: [PATCH 071/973] import Logger from logging --- rss_reader/logger/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 11012766..66daa19f 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,4 +1,6 @@ +from logging import Logger as LG + from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig From bbc957036fbb26a83670a6e94512ae98bad378d2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:52:56 +0300 Subject: [PATCH 072/973] set return value for the setup_logger --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 66daa19f..4a0a8f27 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -20,7 +20,7 @@ def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: Logger.NAME_LOGGER = name_loger self._config = config - def setup_logger(self): + def setup_logger(self) -> LG: return self._config.set_config(Logger.NAME_LOGGER) def get_logger(cls, module_name: str): From 6ccd59b96ca1475ccdd036fe84e9855142b5e1ac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:55:57 +0300 Subject: [PATCH 073/973] add documentation to the function setup_logger --- rss_reader/logger/logger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 4a0a8f27..24e908f0 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -21,6 +21,11 @@ def __init__(self, name_loger: str, config: ISetLoggerConfig) -> None: self._config = config def setup_logger(self) -> LG: + """Set logger configuration. + + :return: object Logger + :rtype: LG + """ return self._config.set_config(Logger.NAME_LOGGER) def get_logger(cls, module_name: str): From 151f31d9dea83fd6d64e044ee26c1db20d6d0c21 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 14:57:22 +0300 Subject: [PATCH 074/973] set return value to the get_logger --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 24e908f0..81139d2d 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -28,5 +28,5 @@ def setup_logger(self) -> LG: """ return self._config.set_config(Logger.NAME_LOGGER) - def get_logger(cls, module_name: str): + def get_logger(cls, module_name: str) -> LG: pass From 4415b94a01b15694ee9326f7f5adea488304f442 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 15:00:38 +0300 Subject: [PATCH 075/973] return logger by name --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 81139d2d..4c926d54 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -29,4 +29,4 @@ def setup_logger(self) -> LG: return self._config.set_config(Logger.NAME_LOGGER) def get_logger(cls, module_name: str) -> LG: - pass + return getLogger(cls.NAME_LOGGER).getChild(module_name) From b046767766f1aa60a81c76d76e60377b04d87744 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 15:01:22 +0300 Subject: [PATCH 076/973] import getLogger from logging --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 4c926d54..06992a5d 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,5 +1,5 @@ -from logging import Logger as LG +from logging import getLogger, Logger as LG from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig From 5bb6985a3d987a619cb4391c0c9bc9ff695a1ae0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 15:02:13 +0300 Subject: [PATCH 077/973] add @classmethod to the get_logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 06992a5d..8362c74c 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -28,5 +28,6 @@ def setup_logger(self) -> LG: """ return self._config.set_config(Logger.NAME_LOGGER) + @classmethod def get_logger(cls, module_name: str) -> LG: return getLogger(cls.NAME_LOGGER).getChild(module_name) From 6a6b0d8ea8cad686758d96a6107a76db8b39b5c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 15:04:19 +0300 Subject: [PATCH 078/973] add docstring to the get_loger --- rss_reader/logger/logger.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 8362c74c..743efd44 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -30,4 +30,11 @@ def setup_logger(self) -> LG: @classmethod def get_logger(cls, module_name: str) -> LG: + """Get logger by name. + + :param module_name: Logger name. + :type module_name: str + :return: object Logger + :rtype: LG + """ return getLogger(cls.NAME_LOGGER).getChild(module_name) From 0916456ff7f70040a63a78249fb7908b20384fae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:03:18 +0300 Subject: [PATCH 079/973] create StreamHandlerConfig class --- rss_reader/logger/logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 743efd44..814cb57d 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -4,6 +4,10 @@ from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig +class StreamHandlerConfig(ISetLoggerConfig): + pass + + class Logger: """Logs data.""" From 1b2cfadc5203a0e884398ce6d41ff497935000aa Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:04:59 +0300 Subject: [PATCH 080/973] create set_config method in the StreamHandlerConfig class --- rss_reader/logger/logger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 814cb57d..47a7428b 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -5,7 +5,9 @@ class StreamHandlerConfig(ISetLoggerConfig): - pass + + def set_config(self, name: str) -> LG: + pass class Logger: From a2a336ebe890e36c7cf8677e83365cae4436dbb2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:05:58 +0300 Subject: [PATCH 081/973] get a logger with the specified name --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 47a7428b..8a66d301 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -7,7 +7,7 @@ class StreamHandlerConfig(ISetLoggerConfig): def set_config(self, name: str) -> LG: - pass + logger = getLogger(name) class Logger: From ca57b559a7b75c8386107730a178df6b5f3e089e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:07:50 +0300 Subject: [PATCH 082/973] set logger StreamHandlerConfig to level DEBUG --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 8a66d301..be344552 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -8,6 +8,7 @@ class StreamHandlerConfig(ISetLoggerConfig): def set_config(self, name: str) -> LG: logger = getLogger(name) + logger.setLevel(DBG) class Logger: From 05358c6f9136fb909778ee16f561d90989d80440 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:09:45 +0300 Subject: [PATCH 083/973] import DEBUG from logging --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index be344552..436bd53a 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,5 +1,5 @@ -from logging import getLogger, Logger as LG +from logging import getLogger, Logger as LG, DEBUG as DBG from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig From 8b88d1ab75c23c2fe379e2a94ae5d859399dd503 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:12:07 +0300 Subject: [PATCH 084/973] create StreamHandler --- rss_reader/logger/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 436bd53a..c8e2aa32 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -9,6 +9,8 @@ class StreamHandlerConfig(ISetLoggerConfig): def set_config(self, name: str) -> LG: logger = getLogger(name) logger.setLevel(DBG) + sh = StreamHandler() + sh.setLevel(DBG) class Logger: From 394b1396eacacab513410ada8cb773bc34e2bada Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:12:58 +0300 Subject: [PATCH 085/973] import StreamHandler from logging --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index c8e2aa32..1e84bd18 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,5 +1,5 @@ -from logging import getLogger, Logger as LG, DEBUG as DBG +from logging import getLogger, StreamHandler, Logger as LG, DEBUG as DBG from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig From 9de9fde5f02f7fb14d0b36a83377380e5b17e9a4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:22:31 +0300 Subject: [PATCH 086/973] create a list of attributes for the logger format string --- rss_reader/logger/logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 1e84bd18..f036e3a3 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -11,6 +11,8 @@ def set_config(self, name: str) -> LG: logger.setLevel(DBG) sh = StreamHandler() sh.setLevel(DBG) + li = ['%(asctime)s', '%(name)s', '%(levelname)s', '%(funcName)s', + '%(lineno)d', '%(message)s'] class Logger: From 4254301c7679526c6773d7f953b2b32abee8e8ab Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:23:34 +0300 Subject: [PATCH 087/973] combine logger formatting attributes into one line --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index f036e3a3..71a0e8bb 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -13,6 +13,7 @@ def set_config(self, name: str) -> LG: sh.setLevel(DBG) li = ['%(asctime)s', '%(name)s', '%(levelname)s', '%(funcName)s', '%(lineno)d', '%(message)s'] + str_f = '|'.join(li) class Logger: From f6f84c57ef453099e7fdd5b277c346396d1da047 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:24:12 +0300 Subject: [PATCH 088/973] create Formatter for StreamHandlerConfig --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 71a0e8bb..a468da72 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -14,6 +14,7 @@ def set_config(self, name: str) -> LG: li = ['%(asctime)s', '%(name)s', '%(levelname)s', '%(funcName)s', '%(lineno)d', '%(message)s'] str_f = '|'.join(li) + formatter = Formatter(str_f, '%Y-%m-%d %H:%M:%S') class Logger: From db44d2d6e215bee9c79e05badca7ca1451572f3a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:25:13 +0300 Subject: [PATCH 089/973] import Formatter from logging --- rss_reader/logger/logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index a468da72..d0894e03 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,5 +1,6 @@ -from logging import getLogger, StreamHandler, Logger as LG, DEBUG as DBG +from logging import (getLogger, StreamHandler, Formatter, + Logger as LG, DEBUG as DBG) from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig From 397c41b198d58147896ceaf3d1c5216c0884f287 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:26:19 +0300 Subject: [PATCH 090/973] set Formatter for the StreamHandler --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index d0894e03..fb749aac 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -16,6 +16,7 @@ def set_config(self, name: str) -> LG: '%(lineno)d', '%(message)s'] str_f = '|'.join(li) formatter = Formatter(str_f, '%Y-%m-%d %H:%M:%S') + sh.setFormatter(formatter) class Logger: From 53f54e645b0eda6e2712ade69fd3ca72faaceff8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:27:49 +0300 Subject: [PATCH 091/973] set set StreamHandler as handler for StreamHandlerConfig --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index fb749aac..6d3fdb80 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -17,6 +17,7 @@ def set_config(self, name: str) -> LG: str_f = '|'.join(li) formatter = Formatter(str_f, '%Y-%m-%d %H:%M:%S') sh.setFormatter(formatter) + logger.addHandler(sh) class Logger: From cc9ac9b784df8cb2aa98549cf4e190057b6d0c8f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:28:17 +0300 Subject: [PATCH 092/973] return logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 6d3fdb80..551f8e30 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -18,6 +18,7 @@ def set_config(self, name: str) -> LG: formatter = Formatter(str_f, '%Y-%m-%d %H:%M:%S') sh.setFormatter(formatter) logger.addHandler(sh) + return logger class Logger: From 9c88543a0adf2d10ac7a4503dd4025c37f8551c1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:30:12 +0300 Subject: [PATCH 093/973] add a docstring for the StreamHandlerConfig --- rss_reader/logger/logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 551f8e30..3462d90c 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -6,6 +6,10 @@ class StreamHandlerConfig(ISetLoggerConfig): + """Basic configuration. + + Sets up logging with the output of the result on the screen. + """ def set_config(self, name: str) -> LG: logger = getLogger(name) From 444e8cb0dc5e24c25766bca17650e0c170fa9246 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:31:30 +0300 Subject: [PATCH 094/973] add a docstring for set_config from StreamHandlerConfig --- rss_reader/logger/logger.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 3462d90c..fe1c4079 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -12,6 +12,14 @@ class StreamHandlerConfig(ISetLoggerConfig): """ def set_config(self, name: str) -> LG: + """Set logger configuration. + + :param name: Logger name. + :type name: str + :return: object Logger. + :rtype: LG + """ + logger = getLogger(name) logger.setLevel(DBG) sh = StreamHandler() From 5643632674d7fdd997d8a45393d4a9aa71f9327d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:39:56 +0300 Subject: [PATCH 095/973] create NullHandlerConfig class --- rss_reader/logger/logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index fe1c4079..8cad5a58 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -33,6 +33,10 @@ def set_config(self, name: str) -> LG: return logger +class NullHandlerConfig(ISetLoggerConfig): + pass + + class Logger: """Logs data.""" From e79b68458e740badbe34002db48cb6e29ec8434a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:40:53 +0300 Subject: [PATCH 096/973] create set_confin into NullHandlerConfig --- rss_reader/logger/logger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 8cad5a58..754416ba 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -34,7 +34,9 @@ def set_config(self, name: str) -> LG: class NullHandlerConfig(ISetLoggerConfig): - pass + + def set_config(self, name: str) -> LG: + pass class Logger: From c4bda53f406655a836ef4d9e29ac3308af63995a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:41:38 +0300 Subject: [PATCH 097/973] get Logger from logging --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 754416ba..b232d1d3 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -36,7 +36,7 @@ def set_config(self, name: str) -> LG: class NullHandlerConfig(ISetLoggerConfig): def set_config(self, name: str) -> LG: - pass + logger = getLogger(name) class Logger: From 54ddd4824eefb3596352a09a31bbacd549860bda Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:42:38 +0300 Subject: [PATCH 098/973] set DEBUG loglevel from Logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index b232d1d3..14e38a98 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -37,6 +37,7 @@ class NullHandlerConfig(ISetLoggerConfig): def set_config(self, name: str) -> LG: logger = getLogger(name) + logger.setLevel(DBG) class Logger: From 09819e4b0edc075571b585db39bf601684d30288 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:43:14 +0300 Subject: [PATCH 099/973] create NullHandler --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 14e38a98..7c4d14d9 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -38,6 +38,7 @@ class NullHandlerConfig(ISetLoggerConfig): def set_config(self, name: str) -> LG: logger = getLogger(name) logger.setLevel(DBG) + sh = NullHandler() class Logger: From 384c9c2ce950a6df433a066473ef8904bbd962f6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:43:55 +0300 Subject: [PATCH 100/973] import NullHandler from logging --- rss_reader/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 7c4d14d9..2fbc1338 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,6 +1,6 @@ from logging import (getLogger, StreamHandler, Formatter, - Logger as LG, DEBUG as DBG) + NullHandler, Logger as LG, DEBUG as DBG) from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig From b2eec8623e3c0b5be984e896379deb17f4fb73e1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:45:26 +0300 Subject: [PATCH 101/973] set DEBUG level for Logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 2fbc1338..e86d8938 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -39,6 +39,7 @@ def set_config(self, name: str) -> LG: logger = getLogger(name) logger.setLevel(DBG) sh = NullHandler() + sh.setLevel(DBG) class Logger: From 1a79432153aa541bef5581d0ce5e7ed979badf2d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:46:10 +0300 Subject: [PATCH 102/973] add NullHandler to the Logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index e86d8938..bfe44099 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -40,6 +40,7 @@ def set_config(self, name: str) -> LG: logger.setLevel(DBG) sh = NullHandler() sh.setLevel(DBG) + logger.addHandler(sh) class Logger: From 0d92ddf0c0d8def31f8365fffee9b7e75d5e01e6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:46:47 +0300 Subject: [PATCH 103/973] return logger --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index bfe44099..2ddb209c 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -41,6 +41,7 @@ def set_config(self, name: str) -> LG: sh = NullHandler() sh.setLevel(DBG) logger.addHandler(sh) + return logger class Logger: From 7c6c895d135614b3bffdb3ea266f7f705dc1bea3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:48:49 +0300 Subject: [PATCH 104/973] add a docstring to the NullHandlerConfig --- rss_reader/logger/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 2ddb209c..bfb8c154 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -34,6 +34,7 @@ def set_config(self, name: str) -> LG: class NullHandlerConfig(ISetLoggerConfig): + """Logger configuration with output to the void.""" def set_config(self, name: str) -> LG: logger = getLogger(name) From b909114666c1e7d51588529f15c05d60728f7fbe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:50:00 +0300 Subject: [PATCH 105/973] add a docstring to the set_config from NullHandlerConfig --- rss_reader/logger/logger.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index bfb8c154..5e961b9f 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -37,6 +37,14 @@ class NullHandlerConfig(ISetLoggerConfig): """Logger configuration with output to the void.""" def set_config(self, name: str) -> LG: + """Set logger configuration. + + :param name: Logger name. + :type name: str + :return: object Logger. + :rtype: LG + """ + logger = getLogger(name) logger.setLevel(DBG) sh = NullHandler() From 9ec6ab6d3199ed3dafa1d9e035088a18cff78c97 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:52:18 +0300 Subject: [PATCH 106/973] add a docstring to the logger module --- rss_reader/logger/logger.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/logger/logger.py b/rss_reader/logger/logger.py index 5e961b9f..72d0f021 100644 --- a/rss_reader/logger/logger.py +++ b/rss_reader/logger/logger.py @@ -1,3 +1,13 @@ +"""This module contains classes for working with data logging. + +The module implements the Logger class, which provides basic functionality +for the functioning of the data logging system. + +The module has two standard classes that serve as system customizers +logging. + +""" + from logging import (getLogger, StreamHandler, Formatter, NullHandler, Logger as LG, DEBUG as DBG) From 2ce18fa72b633b61a055969af52b13e5ecbfea0a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:54:36 +0300 Subject: [PATCH 107/973] add a docstring to the ISetLoggerConfig --- rss_reader/interfaces/ilogger/ilogger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/ilogger/ilogger.py b/rss_reader/interfaces/ilogger/ilogger.py index 0e0103b4..035e2515 100644 --- a/rss_reader/interfaces/ilogger/ilogger.py +++ b/rss_reader/interfaces/ilogger/ilogger.py @@ -6,6 +6,8 @@ class ISetLoggerConfig(ABC): + """Logger configuration setup interface.""" + @abstractmethod def set_config(self, name: str) -> LG: """Set logger configuration. From 8420f57948ad926cf4a1b205fac0adb9b9e1b3c1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:56:14 +0300 Subject: [PATCH 108/973] create create_logger function --- rss_reader/starter/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 3482dab9..c839741d 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -51,3 +51,7 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: namespace_ = parser.parse_args(args) return vars(namespace_) + + +def create_logger(verbose: Optional[str]) -> None: + pass From 758af0a0c80624242360daaaf68cfc5d25123e76 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 16:59:56 +0300 Subject: [PATCH 109/973] create a handler selection for logging --- rss_reader/starter/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index c839741d..97ffd9fb 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -54,4 +54,7 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: def create_logger(verbose: Optional[str]) -> None: - pass + if verbose: + config = StreamHandlerConfig() + else: + config = NullHandlerConfig() From 6329bae2ee8516fcc6d1bb13fe5508362ac6e29c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:01:03 +0300 Subject: [PATCH 110/973] create Logger for the current project --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 97ffd9fb..f31ba054 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -58,3 +58,5 @@ def create_logger(verbose: Optional[str]) -> None: config = StreamHandlerConfig() else: config = NullHandlerConfig() + + Logger(NAME_LOGGER, config).setup_logger() From 51e3abe7cf349a98fb29bfa09a1e5ccf400a7ddc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:01:37 +0300 Subject: [PATCH 111/973] import Optional from typing --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index f31ba054..5c5a8a2e 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -5,7 +5,7 @@ import argparse -from typing import Dict +from typing import Dict, Optional NAME_LOGGER = 'rss-reader' From 6dcc60dc8fd3acc9a6c73a5cc17fe9f41f7650a9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:03:39 +0300 Subject: [PATCH 112/973] import Logger from rss-reader --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 5c5a8a2e..a9d3ce5c 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -7,6 +7,8 @@ import argparse from typing import Dict, Optional +from rss_reader.logger.logger import Logger + NAME_LOGGER = 'rss-reader' version = '0.0.1' From d095801367d15e69d74c5bfcfc6cad1148539f83 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:04:09 +0300 Subject: [PATCH 113/973] import StreamHandlerConfig from rss-reader --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index a9d3ce5c..8f95af67 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -7,7 +7,7 @@ import argparse from typing import Dict, Optional -from rss_reader.logger.logger import Logger +from rss_reader.logger.logger import Logger, StreamHandlerConfig NAME_LOGGER = 'rss-reader' From 967443db4d12c69545b635a3a0788aac01b98968 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:07:21 +0300 Subject: [PATCH 114/973] import NullHandlerConfig from rss-reader --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 8f95af67..c77ae5fa 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -7,7 +7,8 @@ import argparse from typing import Dict, Optional -from rss_reader.logger.logger import Logger, StreamHandlerConfig +from rss_reader.logger.logger import (Logger, StreamHandlerConfig, + NullHandlerConfig) NAME_LOGGER = 'rss-reader' From 48383aedcdc6e67a937672782b2c771ed9b6b852 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:31:02 +0300 Subject: [PATCH 115/973] add type for variable config --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index c77ae5fa..d4e6c0ba 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -58,7 +58,7 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: def create_logger(verbose: Optional[str]) -> None: if verbose: - config = StreamHandlerConfig() + config: ISetLoggerConfig = StreamHandlerConfig() else: config = NullHandlerConfig() From 34383891441bc5de5688f0b6e9db1105f6e893e8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:32:36 +0300 Subject: [PATCH 116/973] import from ISetLoggerConfig from rss-reader --- rss_reader/starter/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index d4e6c0ba..4109a5d3 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -9,6 +9,7 @@ from rss_reader.logger.logger import (Logger, StreamHandlerConfig, NullHandlerConfig) +from rss_reader.interfaces.ilogger.ilogger import ISetLoggerConfig NAME_LOGGER = 'rss-reader' From 2987592779f2eb2120f217c579fdb5fb54eec146 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:37:33 +0300 Subject: [PATCH 117/973] add a dockstring to the logger package --- rss_reader/logger/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/__init__.py b/rss_reader/logger/__init__.py index e69de29b..b3ee2e23 100644 --- a/rss_reader/logger/__init__.py +++ b/rss_reader/logger/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with logging.""" From 5c3298e99b9d92deb60a737cbc70724b68923ec8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:39:17 +0300 Subject: [PATCH 118/973] import create_logger from rss --- rss_reader/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index a44a577f..f4e14730 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -1,7 +1,8 @@ """Program entry point.""" -from rss_reader.starter.base import init_arguments_functionality as iaf +from rss_reader.starter.base import (create_logger, + init_arguments_functionality as iaf) def main(): From 00ca0d9a5a1050808d19803b813e6434203a57e6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:41:33 +0300 Subject: [PATCH 119/973] create a logger with parameters set by the user --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index f4e14730..9c57700a 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -7,6 +7,7 @@ def main(): args = iaf() + create_logger(args.get('verbose')) if __name__ == "__main__": From 0705d2c1b998d15fc5d738b19c08f27da011b7f2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:44:08 +0300 Subject: [PATCH 120/973] create starter.py --- rss_reader/starter/starter.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/starter/starter.py diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py new file mode 100644 index 00000000..e69de29b From aacfc8560f4785aa3c56a57e92ae5ad6941284dd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:47:54 +0300 Subject: [PATCH 121/973] create class Starter --- rss_reader/starter/starter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index e69de29b..7fcfbf20 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -0,0 +1,4 @@ + + +class Starter: + pass From c700e2a3ea827a034bdc41b25745dca12d1b9418 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:49:50 +0300 Subject: [PATCH 122/973] add __init__ to the Starter --- rss_reader/starter/starter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 7fcfbf20..76b0b796 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -1,4 +1,10 @@ class Starter: - pass + def __init__(self, argv: Dict[str, str]) -> None: + """Initializer. + + :param argv: Command line parameter dictionary. + :type argv: Dict[str, str] + """ + self._argv = argv From d1f8e692495032f321fe256861b41a9ed6407d9c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:50:29 +0300 Subject: [PATCH 123/973] create run method --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 76b0b796..5f7332e0 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -8,3 +8,6 @@ def __init__(self, argv: Dict[str, str]) -> None: :type argv: Dict[str, str] """ self._argv = argv + + def run(self) -> None: + pass From 03aee18b68eb237cb27d4a3fb2d5e18b35d3f465 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:51:01 +0300 Subject: [PATCH 124/973] import Dict from typing --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 5f7332e0..b04fc6c0 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -1,5 +1,8 @@ +from typing import Dict + + class Starter: def __init__(self, argv: Dict[str, str]) -> None: """Initializer. From e550bac11c76ca625ffa70b1a1265b951d384a7a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:53:31 +0300 Subject: [PATCH 125/973] import Logger from rss-reader --- rss_reader/starter/starter.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index b04fc6c0..6015fdab 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -2,6 +2,8 @@ from typing import Dict +from rss_reader.logger.logger import Logger + class Starter: def __init__(self, argv: Dict[str, str]) -> None: @@ -11,6 +13,3 @@ def __init__(self, argv: Dict[str, str]) -> None: :type argv: Dict[str, str] """ self._argv = argv - - def run(self) -> None: - pass From e5e6e6ac765dd0bb296f8e3621737387fbccd82e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:55:34 +0300 Subject: [PATCH 126/973] import Logger from rss-reader --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 6015fdab..4a7a8cb2 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -13,3 +13,6 @@ def __init__(self, argv: Dict[str, str]) -> None: :type argv: Dict[str, str] """ self._argv = argv + + def run(self) -> None: + pass From 1f695ff3740f15de27d823b06f7e910325ac61c0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:56:46 +0300 Subject: [PATCH 127/973] get Logger --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 4a7a8cb2..e9a25e45 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -5,6 +5,9 @@ from rss_reader.logger.logger import Logger +log = Logger.get_logger(__name__) + + class Starter: def __init__(self, argv: Dict[str, str]) -> None: """Initializer. From ded7d1686d3eca7a0cda781c6eecae10d86cf771 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 17:59:43 +0300 Subject: [PATCH 128/973] set a log about the start of receiving the number of news --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index e9a25e45..90bec857 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -18,4 +18,4 @@ def __init__(self, argv: Dict[str, str]) -> None: self._argv = argv def run(self) -> None: - pass + log.info("Get the number of requested news.") From b41789c6548483e4e21d73c119000f9f9f9d4246 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:00:34 +0300 Subject: [PATCH 129/973] get number of a news --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 90bec857..23fbc628 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -19,3 +19,6 @@ def __init__(self, argv: Dict[str, str]) -> None: def run(self) -> None: log.info("Get the number of requested news.") + + lim = self._argv.get('limit') + limit = int(lim) if lim else None From bf0b45a142dc781e08d9e03f596692a0713d813f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:02:24 +0300 Subject: [PATCH 130/973] set a log about the end of receiving the number of news --- rss_reader/starter/starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 23fbc628..28691979 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -22,3 +22,5 @@ def run(self) -> None: lim = self._argv.get('limit') limit = int(lim) if lim else None + + log.info("Number was received.") From a3e2322b29df57aabd083ec19441e4980574a492 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:03:28 +0300 Subject: [PATCH 131/973] create ecxeptions.py --- rss_reader/starter/ecxeptions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/starter/ecxeptions.py diff --git a/rss_reader/starter/ecxeptions.py b/rss_reader/starter/ecxeptions.py new file mode 100644 index 00000000..e69de29b From 7850d77fed1629d894a8fd5d08f8db02223b8dc6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:04:19 +0300 Subject: [PATCH 132/973] create NonNumericError --- rss_reader/starter/ecxeptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/ecxeptions.py b/rss_reader/starter/ecxeptions.py index e69de29b..383327c5 100644 --- a/rss_reader/starter/ecxeptions.py +++ b/rss_reader/starter/ecxeptions.py @@ -0,0 +1,3 @@ + +class NonNumericError(Exception): + pass From 006fbaa0d21568b4c5087bff28f926dac0b38c30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:05:11 +0300 Subject: [PATCH 133/973] add a docstring to the NonNumericError --- rss_reader/starter/ecxeptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/ecxeptions.py b/rss_reader/starter/ecxeptions.py index 383327c5..71016cf6 100644 --- a/rss_reader/starter/ecxeptions.py +++ b/rss_reader/starter/ecxeptions.py @@ -1,3 +1,4 @@ class NonNumericError(Exception): + """Cannot convert incoming value to int.""" pass From 293e8ab7083efb29e5758973d19dfa2bad2566c5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:06:42 +0300 Subject: [PATCH 134/973] add a docstring to the exceptions module --- rss_reader/starter/ecxeptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/ecxeptions.py b/rss_reader/starter/ecxeptions.py index 71016cf6..0e9e5fab 100644 --- a/rss_reader/starter/ecxeptions.py +++ b/rss_reader/starter/ecxeptions.py @@ -1,3 +1,5 @@ +"""This module contains custom exceptions for the starter package.""" + class NonNumericError(Exception): """Cannot convert incoming value to int.""" From 6d2ac1d4db93a4618dbc5fd26e2bfe671e81680d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:07:32 +0300 Subject: [PATCH 135/973] import NonNumericError from rss-reader --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 28691979..fb011649 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -3,7 +3,7 @@ from typing import Dict from rss_reader.logger.logger import Logger - +from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) From 25c4677cf6ae3a9be264531a351d719bf888cf34 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:09:03 +0300 Subject: [PATCH 136/973] catch NonNumericError error --- rss_reader/starter/starter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index fb011649..1519440d 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -20,7 +20,11 @@ def __init__(self, argv: Dict[str, str]) -> None: def run(self) -> None: log.info("Get the number of requested news.") - lim = self._argv.get('limit') - limit = int(lim) if lim else None + try: + lim = self._argv.get('limit') + limit = int(lim) if lim else None + except ValueError as e: + log.exception(e) + raise NonNumericError("--limit has a non-numeric value") from e log.info("Number was received.") From 29fb4bb8e8b12d0be559d2dec51638b9f60314c5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:12:05 +0300 Subject: [PATCH 137/973] create _get_data_from_resource method --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 1519440d..eaa92e75 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -28,3 +28,6 @@ def run(self) -> None: raise NonNumericError("--limit has a non-numeric value") from e log.info("Number was received.") + + def _get_data_from_resource(self): + pass From 0e672bd9dbcabfe12910eb1ce172ba0820d31713 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:12:56 +0300 Subject: [PATCH 138/973] create __init__ into iloader --- rss_reader/interfaces/iloader/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iloader/__init__.py diff --git a/rss_reader/interfaces/iloader/__init__.py b/rss_reader/interfaces/iloader/__init__.py new file mode 100644 index 00000000..e69de29b From 0945148ac05c80119475af65159f915ac165d153 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:13:40 +0300 Subject: [PATCH 139/973] create iloader.py --- rss_reader/interfaces/iloader/iloader.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iloader/iloader.py diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py new file mode 100644 index 00000000..e69de29b From e713e3be8285adc27b921dfe09427685eec60904 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:14:10 +0300 Subject: [PATCH 140/973] import ABC from abc --- rss_reader/interfaces/iloader/iloader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index e69de29b..ef98bf7e 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -0,0 +1,2 @@ + +from abc import ABC From f37583f1e212ed5d19f924fb937ce885d7d68795 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:14:40 +0300 Subject: [PATCH 141/973] import abstractmethod from abc --- rss_reader/interfaces/iloader/iloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index ef98bf7e..78901a93 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -1,2 +1,2 @@ -from abc import ABC +from abc import ABC, abstractmethod From bce3aafbb8e44e55182892b1547a40b4695dd5b6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:15:22 +0300 Subject: [PATCH 142/973] create IHandler class --- rss_reader/interfaces/iloader/iloader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index 78901a93..f2a59eaf 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -1,2 +1,6 @@ from abc import ABC, abstractmethod + + +class IHandler(ABC): + pass From 90d8e7000697f55e8364a58567cebdfb4e869dd7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:16:14 +0300 Subject: [PATCH 143/973] create get_data method into IHandler --- rss_reader/interfaces/iloader/iloader.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index f2a59eaf..3cdad10c 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -3,4 +3,9 @@ class IHandler(ABC): - pass + + def get_data(self, tag_name: str, + title_tag: str, + source: str, + limit: int) -> dict: + pass From c415773b704f4ddd651b1021a22d3a848e0d4b8a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:22:11 +0300 Subject: [PATCH 144/973] add a docstring to the get_data --- rss_reader/interfaces/iloader/iloader.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index 3cdad10c..2b33c690 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -8,4 +8,18 @@ def get_data(self, tag_name: str, title_tag: str, source: str, limit: int) -> dict: + """Return a dictionary with parsed data. + + :param tag_name: The name of the tag in which the news is stored. + :type tag_name: str + :param title_tag: The name of the tag in which the name + of the rss resource is stored. + :type title_tag: str + :param source: Resource URL. + :type source: str + :param limit: Number of news items to display. + :type limit: int + :return: Dictionary with parsed data. + :rtype: dict + """ pass From 49283a55e07c868eec812c49350a7324d00f768b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:22:53 +0300 Subject: [PATCH 145/973] add dockstring to the IHandler --- rss_reader/interfaces/iloader/iloader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index 2b33c690..dcecd817 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -3,6 +3,7 @@ class IHandler(ABC): + """Interface for receiving data.""" def get_data(self, tag_name: str, title_tag: str, From 9157554aff3896096fe1c493da10d058eb585761 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:23:35 +0300 Subject: [PATCH 146/973] add docstring to the iloader module --- rss_reader/interfaces/iloader/iloader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index dcecd817..e2b45918 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -1,3 +1,5 @@ +"""This module contains a set of interfaces for the loader.""" + from abc import ABC, abstractmethod From 01868d8cc4431ae34040c3876be2beef9bb723d1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:25:39 +0300 Subject: [PATCH 147/973] add docstring to the iloader package --- rss_reader/interfaces/iloader/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iloader/__init__.py b/rss_reader/interfaces/iloader/__init__.py index e69de29b..f7e553be 100644 --- a/rss_reader/interfaces/iloader/__init__.py +++ b/rss_reader/interfaces/iloader/__init__.py @@ -0,0 +1 @@ +"""This package contains the loader interfaces.""" From 1e813326a21ba2582f3b540aba431efbeec3b89d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:29:01 +0300 Subject: [PATCH 148/973] create __init__ into loader package --- rss_reader/loader/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/loader/__init__.py diff --git a/rss_reader/loader/__init__.py b/rss_reader/loader/__init__.py new file mode 100644 index 00000000..e69de29b From e9078e269f54e2e9dc55d4d4219f435cfcd5944a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:33:02 +0300 Subject: [PATCH 149/973] add a dockstring to the loader package --- rss_reader/loader/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/__init__.py b/rss_reader/loader/__init__.py index e69de29b..fcd0b073 100644 --- a/rss_reader/loader/__init__.py +++ b/rss_reader/loader/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with data loaders.""" From 9e765657928a22f2ec7490d2dd3dffcec34b954b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 18:33:56 +0300 Subject: [PATCH 150/973] create loader.py --- rss_reader/loader/loader.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/loader/loader.py diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py new file mode 100644 index 00000000..e69de29b From 14e9a7d8d7bbb79bcd95af58e6b8fe13b5015656 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 20:43:16 +0300 Subject: [PATCH 151/973] import Logger from rss-reader --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index e69de29b..b81e71a8 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -0,0 +1,2 @@ + +from rss_reader.logger.logger import Logger From e4f7784c6196a75a7fd78cd5ce872b28568e5b88 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 20:45:18 +0300 Subject: [PATCH 152/973] get logger of the program --- rss_reader/loader/loader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index b81e71a8..8cd28263 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -1,2 +1,5 @@ from rss_reader.logger.logger import Logger + + +log = Logger.get_logger(__name__) From 594130073e5660aaa6dd2b47d79c5cd3846cd942 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 20:45:53 +0300 Subject: [PATCH 153/973] create FromWebHandler class --- rss_reader/loader/loader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 8cd28263..55f95116 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -3,3 +3,7 @@ log = Logger.get_logger(__name__) + + +class FromWebHandler(IHandler): + pass From 2d07b9b048fdfb86079da22b6e5eadc2e49f77bc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:00:54 +0300 Subject: [PATCH 154/973] import IHandler from rss-reader --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 55f95116..c1839e11 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -1,4 +1,6 @@ + +from rss_reader.interfaces.iloader.iloader import IHandler from rss_reader.logger.logger import Logger From 5f065efd8a786c9d7e816c4c2b637a8b2e38a100 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:07:27 +0300 Subject: [PATCH 155/973] create __init__ into FromWebHandler class --- rss_reader/loader/loader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index c1839e11..1fe03b1e 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -8,4 +8,7 @@ class FromWebHandler(IHandler): - pass + def __init__(self, + crawler: ICrawler, + parser: IParser) -> None: + pass From fc4e5d05dafe1b90bdcd5be6fc7cbc1ce0258cfa Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:12:21 +0300 Subject: [PATCH 156/973] add _crawler to the class instance variable --- rss_reader/loader/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 1fe03b1e..0d4b7ea3 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -11,4 +11,4 @@ class FromWebHandler(IHandler): def __init__(self, crawler: ICrawler, parser: IParser) -> None: - pass + self._crawler = crawler From 7da5512bb8e80c18ff06db1c509edd0f9624a91e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:12:42 +0300 Subject: [PATCH 157/973] add _parser to the class instance variable --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 0d4b7ea3..30860dba 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -12,3 +12,4 @@ def __init__(self, crawler: ICrawler, parser: IParser) -> None: self._crawler = crawler + self._parser = parser From c45447c64c17837444a8ea26f12a0ac745de3395 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:16:49 +0300 Subject: [PATCH 158/973] add a docstring to the __init__ of the FromWebHandler --- rss_reader/loader/loader.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 30860dba..ced78fe8 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -11,5 +11,14 @@ class FromWebHandler(IHandler): def __init__(self, crawler: ICrawler, parser: IParser) -> None: + """Initializer. + + :param crawler: Crawler object. Used to get information + from the Internet. + :type crawler: ICrawler + :param parser: Parser object. Used to parse information + received from the Internet. + :type parser: IParser + """ self._crawler = crawler self._parser = parser From 34aa25c3467da6e9655e41be6ca32a97d317df2f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:18:36 +0300 Subject: [PATCH 159/973] create icrawler package --- rss_reader/interfaces/icrawler/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/icrawler/__init__.py diff --git a/rss_reader/interfaces/icrawler/__init__.py b/rss_reader/interfaces/icrawler/__init__.py new file mode 100644 index 00000000..e69de29b From 65740ecad7dc528d2bf9d298d2195b1ed92bb155 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:20:25 +0300 Subject: [PATCH 160/973] add a docstring to the crawler package --- rss_reader/interfaces/icrawler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/icrawler/__init__.py b/rss_reader/interfaces/icrawler/__init__.py index e69de29b..5a8fcc6c 100644 --- a/rss_reader/interfaces/icrawler/__init__.py +++ b/rss_reader/interfaces/icrawler/__init__.py @@ -0,0 +1 @@ +"""This package contains the crawler interfaces.""" From 7806b14f1235476095e233013c80ad5d6d856682 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:21:06 +0300 Subject: [PATCH 161/973] create icrawler.py --- rss_reader/interfaces/icrawler/icrawler.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/icrawler/icrawler.py diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py new file mode 100644 index 00000000..e69de29b From 2e859ac72d388a7d1ac66c7e2dd69d8a69a02aff Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:21:39 +0300 Subject: [PATCH 162/973] import ABC from abc --- rss_reader/interfaces/icrawler/icrawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index e69de29b..ef98bf7e 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -0,0 +1,2 @@ + +from abc import ABC From baba7dc11868179d6a8c18ed28a00a378d04035a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:22:37 +0300 Subject: [PATCH 163/973] import abstractmethod from abc --- rss_reader/interfaces/icrawler/icrawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index ef98bf7e..78901a93 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -1,2 +1,2 @@ -from abc import ABC +from abc import ABC, abstractmethod From 0a774c43b0047a7b9fa6c6dec49881e546eaf08e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:23:16 +0300 Subject: [PATCH 164/973] create ICrawler class --- rss_reader/interfaces/icrawler/icrawler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index 78901a93..ab04374f 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -1,2 +1,6 @@ from abc import ABC, abstractmethod + + +class ICrawler(ABC): + pass From 448a3898411ccd77852275b9b34977f9c5fa8653 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:24:23 +0300 Subject: [PATCH 165/973] create abstractmethod get_data into ICrawler --- rss_reader/interfaces/icrawler/icrawler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index ab04374f..dfc874c9 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -3,4 +3,5 @@ class ICrawler(ABC): - pass + def get_data(self) -> bytes: + pass From afaee3e84de0a8f4f27b5962a049ac7594e925a1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:26:53 +0300 Subject: [PATCH 166/973] add docstring into get_data method --- rss_reader/interfaces/icrawler/icrawler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index dfc874c9..22bca0de 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -4,4 +4,9 @@ class ICrawler(ABC): def get_data(self) -> bytes: + """Get the content of the requested page. + + :return: Returns the result as a byte string. + :rtype: bytes + """ pass From 7c4afacd427967e60b20894e74ff2d72b0fc40c3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:27:41 +0300 Subject: [PATCH 167/973] add @abstractmethod decorator to the get_data --- rss_reader/interfaces/icrawler/icrawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index 22bca0de..111331f3 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -3,6 +3,8 @@ class ICrawler(ABC): + + @abstractmethod def get_data(self) -> bytes: """Get the content of the requested page. From 051dfba02786fbc352505d732116e32b9a8a27dc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:29:30 +0300 Subject: [PATCH 168/973] add docstring to the ICrawler --- rss_reader/interfaces/icrawler/icrawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index 111331f3..af5e633b 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -3,6 +3,7 @@ class ICrawler(ABC): + """Interface for a crawler.""" @abstractmethod def get_data(self) -> bytes: From ed87c8b7617aba4c2bcb76296f664b281e3d3c4c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:30:21 +0300 Subject: [PATCH 169/973] add docstring to the icrawler module --- rss_reader/interfaces/icrawler/icrawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/icrawler/icrawler.py b/rss_reader/interfaces/icrawler/icrawler.py index af5e633b..81e3c0ca 100644 --- a/rss_reader/interfaces/icrawler/icrawler.py +++ b/rss_reader/interfaces/icrawler/icrawler.py @@ -1,3 +1,4 @@ +"""This module contains a set of interfaces for the crawler.""" from abc import ABC, abstractmethod From dd9a4805c5135b22a8a4463ef200752b078b7c97 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 21:31:31 +0300 Subject: [PATCH 170/973] import ICrawler from rss-reader --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index ced78fe8..36ffffaa 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -1,6 +1,7 @@ from rss_reader.interfaces.iloader.iloader import IHandler +from rss_reader.interfaces.icrawler.icrawler import ICrawler from rss_reader.logger.logger import Logger From 86df553ed97906246a968a6302f03a6041db61fa Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:19:51 +0300 Subject: [PATCH 171/973] create iparser package --- rss_reader/interfaces/iparser/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iparser/__init__.py diff --git a/rss_reader/interfaces/iparser/__init__.py b/rss_reader/interfaces/iparser/__init__.py new file mode 100644 index 00000000..e69de29b From f7e576e45b8f233a03241335ebdda179c0940754 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:48:56 +0300 Subject: [PATCH 172/973] add a dockstring to the iparser package --- rss_reader/interfaces/iparser/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iparser/__init__.py b/rss_reader/interfaces/iparser/__init__.py index e69de29b..f5674640 100644 --- a/rss_reader/interfaces/iparser/__init__.py +++ b/rss_reader/interfaces/iparser/__init__.py @@ -0,0 +1 @@ +"""This package contains the parser interfaces.""" From 0d12f91ecf2a85d188afd6faf0b16706c798773c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:49:43 +0300 Subject: [PATCH 173/973] create iparser module --- rss_reader/interfaces/iparser/iparser.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iparser/iparser.py diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py new file mode 100644 index 00000000..e69de29b From 910a5df707d42974f4676a5ba46dcad21786f9ea Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:50:41 +0300 Subject: [PATCH 174/973] import ABC from abc --- rss_reader/interfaces/iparser/iparser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index e69de29b..03fc4721 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -0,0 +1,2 @@ + +from abc import ABC \ No newline at end of file From d2d9734270b23b7a238f965c2ef526a904ec7f31 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:51:09 +0300 Subject: [PATCH 175/973] import abstractmethod from abc --- rss_reader/interfaces/iparser/iparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 03fc4721..9104f8e4 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -1,2 +1,2 @@ -from abc import ABC \ No newline at end of file +from abc import ABC, abstractmethod \ No newline at end of file From efdb539fbcda78613582e32e4d36efcc35dae5ef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:51:56 +0300 Subject: [PATCH 176/973] create IParser class --- rss_reader/interfaces/iparser/iparser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 9104f8e4..b178ccf2 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -1,2 +1,6 @@ -from abc import ABC, abstractmethod \ No newline at end of file +from abc import ABC, abstractmethod + + +class IParser(ABC): + pass From 693638b07847249085a0ff56ff7eb92bd9d634c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:52:32 +0300 Subject: [PATCH 177/973] create abstractmethod create_parser --- rss_reader/interfaces/iparser/iparser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index b178ccf2..09903457 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -3,4 +3,6 @@ class IParser(ABC): - pass + @abstractmethod + def create_parser(self, markup: bytes, features: str = 'xml') -> None: + pass From f42588ddf22058b378735e43331e0b98c23394f6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:53:16 +0300 Subject: [PATCH 178/973] create abstractmethod get_tags_text --- rss_reader/interfaces/iparser/iparser.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 09903457..42dc80ff 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -6,3 +6,9 @@ class IParser(ABC): @abstractmethod def create_parser(self, markup: bytes, features: str = 'xml') -> None: pass + + @abstractmethod + def get_tags_text(self, + selector: str, + limit_elms: int = None) -> Generator[str, None, None]: + pass From 5cbb04e1bd83da4163140e339c01127dadf852e1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:54:13 +0300 Subject: [PATCH 179/973] create abstractmethod get_items --- rss_reader/interfaces/iparser/iparser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 42dc80ff..8d257ec4 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -12,3 +12,10 @@ def get_tags_text(self, selector: str, limit_elms: int = None) -> Generator[str, None, None]: pass + + @abstractmethod + def get_items(self, + template: dict, + name: str, + limit_elms: int = None) -> List[dict]: + pass From 8e4466261e7e69db321c827b432925ac12bb05a0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:55:09 +0300 Subject: [PATCH 180/973] import Generator from typing --- rss_reader/interfaces/iparser/iparser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 8d257ec4..e782b990 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod +from typing import Generator class IParser(ABC): From a3cc9bf107ff405a41a29a035bbafa48a98d9066 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 22:55:55 +0300 Subject: [PATCH 181/973] import List from typing --- rss_reader/interfaces/iparser/iparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index e782b990..5942efed 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from typing import Generator +from typing import Generator, List class IParser(ABC): From 408ea5cd50b086ec30d296876f819d50b7513d8a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 23:03:17 +0300 Subject: [PATCH 182/973] add a docstring to the create_parser in IParser --- rss_reader/interfaces/iparser/iparser.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 5942efed..86b3b080 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -6,7 +6,18 @@ class IParser(ABC): @abstractmethod def create_parser(self, markup: bytes, features: str = 'xml') -> None: - pass + """Create a parser object. + + :param markup: A string or a file-like object representing markup + to be parsed. + :type markup: bytes + :param features: Desirable features of the parser to be used. This may + be the name of a specific parser ("lxml", "lxml-xml", + "html.parser", or "html5lib") or it may be the type + of markup to be used ("html", "html5", "xml"). + Defaults to 'xml'. + :type features: str, optional + """ @abstractmethod def get_tags_text(self, From 58fd82f19070e72da9ee7503b5cca4af66a9e2bc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 23:11:12 +0300 Subject: [PATCH 183/973] add a dockstring to the get_tags_text in Iparser --- rss_reader/interfaces/iparser/iparser.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 86b3b080..cd57f603 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -23,6 +23,15 @@ def create_parser(self, markup: bytes, features: str = 'xml') -> None: def get_tags_text(self, selector: str, limit_elms: int = None) -> Generator[str, None, None]: + """Returns the text stored in the tag(s). + + :param selector: A string containing a CSS selector. + :type selector: str + :param limit_elms: The number of elements to return, defaults to None. + :type limit_elms: int, optional + :yield: Returns the text of each element. + :rtype: Generator[str, None, None] + """ pass @abstractmethod From 03d5115ab2875549eb0d3e2ed24eedade8d85e34 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 23:36:47 +0300 Subject: [PATCH 184/973] add a docstring to the get_items in IParser --- rss_reader/interfaces/iparser/iparser.py | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index cd57f603..f849807f 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -39,4 +39,32 @@ def get_items(self, template: dict, name: str, limit_elms: int = None) -> List[dict]: + """Get a list of found items. + + :param template: Specifies the element search pattern. Represents a + dictionary. The keys of the dictionary are the tags + to be found, and the value is just a string + (for example, 'text'). If you need to find the + attributes of a tag, then the value is a list that + lists all the attributes you need to search + (for example, Date: Thu, 23 Jun 2022 23:38:46 +0300 Subject: [PATCH 185/973] add a dockstring to the IParser --- rss_reader/interfaces/iparser/iparser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index f849807f..2f60b02a 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -4,6 +4,8 @@ class IParser(ABC): + """Interface for a parser.""" + @abstractmethod def create_parser(self, markup: bytes, features: str = 'xml') -> None: """Create a parser object. From 8902a94ee0cda4c7d10204980fa4985a0ac68ac0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 23 Jun 2022 23:41:19 +0300 Subject: [PATCH 186/973] add a dockstring to the iparser module --- rss_reader/interfaces/iparser/iparser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/iparser/iparser.py b/rss_reader/interfaces/iparser/iparser.py index 2f60b02a..cfdc0cf6 100644 --- a/rss_reader/interfaces/iparser/iparser.py +++ b/rss_reader/interfaces/iparser/iparser.py @@ -1,3 +1,7 @@ +"""This module contains a set of interfaces that describe the work +data parsers. +""" + from abc import ABC, abstractmethod from typing import Generator, List From 3cd54ba4c79fb95e39ac8a353aed064216427483 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:20:16 +0300 Subject: [PATCH 187/973] import IParser from rss-reader --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 36ffffaa..49e12093 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -2,6 +2,7 @@ from rss_reader.interfaces.iloader.iloader import IHandler from rss_reader.interfaces.icrawler.icrawler import ICrawler +from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.logger.logger import Logger From 4fd4a6a7087fa8e8303b139fd4bd5ad7ffca85b6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:23:19 +0300 Subject: [PATCH 188/973] add get_data method to the FromWebHandler --- rss_reader/loader/loader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 49e12093..0cf262a0 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -24,3 +24,10 @@ def __init__(self, """ self._crawler = crawler self._parser = parser + + def get_data(self, + tag_name: str, + title_tag: str, + source: str, + limit: int) -> dict: + pass From 3df913b1b56557b9507c4b59b6e881c3a47e3ff9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:25:28 +0300 Subject: [PATCH 189/973] create a crawler object --- rss_reader/loader/loader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 0cf262a0..2f4a76eb 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -30,4 +30,5 @@ def get_data(self, title_tag: str, source: str, limit: int) -> dict: - pass + + cr = self._crawler(source) From cedb8c6eee6d18eb5acd7bc8bc4a0a5f912561f2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:27:08 +0300 Subject: [PATCH 190/973] get data from the crawler --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 2f4a76eb..eee3a316 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -32,3 +32,4 @@ def get_data(self, limit: int) -> dict: cr = self._crawler(source) + response_ = cr.get_data() From 37cd66f9a4c56f19966878853df48e8f18cdc1d8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:28:19 +0300 Subject: [PATCH 191/973] create a parser based on the response from the crawler --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index eee3a316..95ddd0f3 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -33,3 +33,5 @@ def get_data(self, cr = self._crawler(source) response_ = cr.get_data() + + self._parser.create_parser(markup=response_) From c1f4d7d1d0ed6891e00aeb08894614ea7b746e3a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:29:45 +0300 Subject: [PATCH 192/973] get the text of the main title tag --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 95ddd0f3..b7f6c260 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -35,3 +35,5 @@ def get_data(self, response_ = cr.get_data() self._parser.create_parser(markup=response_) + title_text = next(self._parser.get_tags_text( + selector=title_tag)) From c64c414066f724be4879425a97d05f212a057888 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:31:26 +0300 Subject: [PATCH 193/973] get a list of all requested news --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index b7f6c260..5feb1f24 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -37,3 +37,5 @@ def get_data(self, self._parser.create_parser(markup=response_) title_text = next(self._parser.get_tags_text( selector=title_tag)) + items = self._parser.get_items( + self.template, name=tag_name, limit_elms=limit) From bde92e31cccc545d384d2acbf99279c2e6d1d86f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:33:09 +0300 Subject: [PATCH 194/973] start logging the data generation process --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 5feb1f24..64c7c55f 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -39,3 +39,5 @@ def get_data(self, selector=title_tag)) items = self._parser.get_items( self.template, name=tag_name, limit_elms=limit) + + log.info('Start generating results.') From 7a1b57de132fb3a725f73e9ee2f0e1b1bc99c4a9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:35:12 +0300 Subject: [PATCH 195/973] form a dictionary with the received data --- rss_reader/loader/loader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 64c7c55f..29cc0fff 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -41,3 +41,6 @@ def get_data(self, self.template, name=tag_name, limit_elms=limit) log.info('Start generating results.') + result = {'title_web_resource': title_text} + items_dict = {'items': items} + result.update(items_dict) From a2f0b3298394fa5f4fcfd81a3cd0397e1ff171a9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:36:29 +0300 Subject: [PATCH 196/973] stop the data generation logging process --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 29cc0fff..76f77d7e 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -44,3 +44,4 @@ def get_data(self, result = {'title_web_resource': title_text} items_dict = {'items': items} result.update(items_dict) + log.info('Result was formed.') From f23662f81c0cb63c5998ae68d2c1a694bdfbb697 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:37:34 +0300 Subject: [PATCH 197/973] return received data from get_data --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 76f77d7e..70ad013b 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -45,3 +45,5 @@ def get_data(self, items_dict = {'items': items} result.update(items_dict) log.info('Result was formed.') + + return result From 50d3dc120c4218fb79bfcc9c75b68e60ddd0249c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:40:48 +0300 Subject: [PATCH 198/973] add a dockstring to the get_data from FromWebHandler --- rss_reader/loader/loader.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 70ad013b..bb017167 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -30,6 +30,20 @@ def get_data(self, title_tag: str, source: str, limit: int) -> dict: + """Return a dictionary with parsed data. + + :param tag_name: The name of the tag in which the news is stored. + :type tag_name: str + :param title_tag: The name of the tag in which the name + of the rss resource is stored. + :type title_tag: str + :param source: Resource URL. + :type source: str + :param limit: Number of news items to display. + :type limit: int + :return: Dictionary with parsed data. + :rtype: dict + """ cr = self._crawler(source) response_ = cr.get_data() From 13ad94c4b8a0b8877e27fc898e210d68dd113f99 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:44:23 +0300 Subject: [PATCH 199/973] align indentation in class FromWebHandler --- rss_reader/loader/loader.py | 72 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index bb017167..4c8cfb43 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -25,39 +25,39 @@ def __init__(self, self._crawler = crawler self._parser = parser - def get_data(self, - tag_name: str, - title_tag: str, - source: str, - limit: int) -> dict: - """Return a dictionary with parsed data. - - :param tag_name: The name of the tag in which the news is stored. - :type tag_name: str - :param title_tag: The name of the tag in which the name - of the rss resource is stored. - :type title_tag: str - :param source: Resource URL. - :type source: str - :param limit: Number of news items to display. - :type limit: int - :return: Dictionary with parsed data. - :rtype: dict - """ - - cr = self._crawler(source) - response_ = cr.get_data() - - self._parser.create_parser(markup=response_) - title_text = next(self._parser.get_tags_text( - selector=title_tag)) - items = self._parser.get_items( - self.template, name=tag_name, limit_elms=limit) - - log.info('Start generating results.') - result = {'title_web_resource': title_text} - items_dict = {'items': items} - result.update(items_dict) - log.info('Result was formed.') - - return result + def get_data(self, + tag_name: str, + title_tag: str, + source: str, + limit: int) -> dict: + """Return a dictionary with parsed data. + + :param tag_name: The name of the tag in which the news is stored. + :type tag_name: str + :param title_tag: The name of the tag in which the name + of the rss resource is stored. + :type title_tag: str + :param source: Resource URL. + :type source: str + :param limit: Number of news items to display. + :type limit: int + :return: Dictionary with parsed data. + :rtype: dict + """ + + cr = self._crawler(source) + response_ = cr.get_data() + + self._parser.create_parser(markup=response_) + title_text = next(self._parser.get_tags_text( + selector=title_tag)) + items = self._parser.get_items( + self.template, name=tag_name, limit_elms=limit) + + log.info('Start generating results.') + result = {'title_web_resource': title_text} + items_dict = {'items': items} + result.update(items_dict) + log.info('Result was formed.') + + return result From 29c49dbdfbc0a0cbf009b1097943fc003ccfdeaf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:46:09 +0300 Subject: [PATCH 200/973] add base template to class FromWebHandler --- rss_reader/loader/loader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 4c8cfb43..63d5703c 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -10,6 +10,13 @@ class FromWebHandler(IHandler): + template = {'title': 'text', + 'pubDate': 'text', + 'source': 'text', + 'link': 'text', + 'content': ['url', 'title'] + } + def __init__(self, crawler: ICrawler, parser: IParser) -> None: From 41bca71af7361c6868ffe7012f4f51d7f1952bcb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:49:03 +0300 Subject: [PATCH 201/973] add a docstring to class FromWebHandler --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 63d5703c..8150331f 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -10,6 +10,8 @@ class FromWebHandler(IHandler): + """Internet data handler.""" + template = {'title': 'text', 'pubDate': 'text', 'source': 'text', From 08d90ec849737f9d976e2ce71a452e070e39ed3a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:53:36 +0300 Subject: [PATCH 202/973] create decorator package --- rss_reader/decorator/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/decorator/__init__.py diff --git a/rss_reader/decorator/__init__.py b/rss_reader/decorator/__init__.py new file mode 100644 index 00000000..e69de29b From f9e438a571033b449b0705cd1053bcdb165daa30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 00:56:16 +0300 Subject: [PATCH 203/973] add a docstring to decorator package --- rss_reader/decorator/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/decorator/__init__.py b/rss_reader/decorator/__init__.py index e69de29b..6876cb57 100644 --- a/rss_reader/decorator/__init__.py +++ b/rss_reader/decorator/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with decorators.""" From 1fbb6d158c239d63440e538e8f5d15baeb57fa59 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:04:35 +0300 Subject: [PATCH 204/973] create decorator.py --- rss_reader/decorator/decorator.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/decorator/decorator.py diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py new file mode 100644 index 00000000..e69de29b From b9e545c9b0375407a4017001df96309255cac8ef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:05:50 +0300 Subject: [PATCH 205/973] create send_log_of_start_function function --- rss_reader/decorator/decorator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index e69de29b..11d683fc 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -0,0 +1,4 @@ + + +def send_log_of_start_function(func): + pass From 2ca99a98a5bf5cfa2abf7ac2fd116f856ec8cf6d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:07:16 +0300 Subject: [PATCH 206/973] create an inner function wrapper --- rss_reader/decorator/decorator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index 11d683fc..f13eebac 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -1,4 +1,6 @@ def send_log_of_start_function(func): - pass + def wrapper(*args, **kwargs): + pass + return wrapper From 8b8e026f8d8433ab4a2655cf98d62daefe07222b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:11:10 +0300 Subject: [PATCH 207/973] import Logger from rss-reader --- rss_reader/decorator/decorator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index f13eebac..b7fe327e 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -1,5 +1,8 @@ +from rss_reader.logger.logger import Logger + + def send_log_of_start_function(func): def wrapper(*args, **kwargs): pass From deea3bafebcf79cdab7a259d8218bc622f45920f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:12:12 +0300 Subject: [PATCH 208/973] import wraps from functools --- rss_reader/decorator/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index b7fe327e..1ac70d3d 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -1,5 +1,5 @@ - +from functools import wraps from rss_reader.logger.logger import Logger From 425bcb7ff25eeeece86caa123f9d34e32d41f7b1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:14:46 +0300 Subject: [PATCH 209/973] get program logger --- rss_reader/decorator/decorator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index 1ac70d3d..8caee52d 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -2,6 +2,8 @@ from functools import wraps from rss_reader.logger.logger import Logger +log = Logger.get_logger(__name__) + def send_log_of_start_function(func): def wrapper(*args, **kwargs): From 9f36c3cf89c0419008079d062f31fa3282ed45d0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 08:20:40 +0300 Subject: [PATCH 210/973] log the start of the function execution --- rss_reader/decorator/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index 8caee52d..daf8d5b0 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -7,5 +7,5 @@ def send_log_of_start_function(func): def wrapper(*args, **kwargs): - pass + log.info(f'A {func.__name__} function starts working.') return wrapper From f8a420dd5f249fc46c962b5cb5fc365d11015488 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:03:42 +0300 Subject: [PATCH 211/973] calling the decorated function --- rss_reader/decorator/decorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index daf8d5b0..9665c066 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -8,4 +8,5 @@ def send_log_of_start_function(func): def wrapper(*args, **kwargs): log.info(f'A {func.__name__} function starts working.') + res = func(*args, **kwargs) return wrapper From bb4d42bf1c7a4adb1d4784235b353dcb97e88591 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:08:31 +0300 Subject: [PATCH 212/973] log the end of the function execution --- rss_reader/decorator/decorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index 9665c066..9ba06fdc 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -9,4 +9,5 @@ def send_log_of_start_function(func): def wrapper(*args, **kwargs): log.info(f'A {func.__name__} function starts working.') res = func(*args, **kwargs) + log.info(f'A {func.__name__} function has completed its work.') return wrapper From 3abffcce37fed0aef701c58e930adfdb75874390 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:09:33 +0300 Subject: [PATCH 213/973] return the result of the decorated function --- rss_reader/decorator/decorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index 9ba06fdc..fa4c5210 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -10,4 +10,5 @@ def wrapper(*args, **kwargs): log.info(f'A {func.__name__} function starts working.') res = func(*args, **kwargs) log.info(f'A {func.__name__} function has completed its work.') + return res return wrapper From 8f0d6d35b169e763529d5cbde112c9ae3c4ce0b5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:10:26 +0300 Subject: [PATCH 214/973] add @wraps(func) to send_log_of_start_function --- rss_reader/decorator/decorator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index fa4c5210..2ace85bf 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -6,6 +6,8 @@ def send_log_of_start_function(func): + + @wraps(func) def wrapper(*args, **kwargs): log.info(f'A {func.__name__} function starts working.') res = func(*args, **kwargs) From 03f3e642c69b804ad50fe1aceed4c3da9cbdaac9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:13:38 +0300 Subject: [PATCH 215/973] add a docstring to the send_log_of_start_function function --- rss_reader/decorator/decorator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index 2ace85bf..cb1efce2 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -6,6 +6,10 @@ def send_log_of_start_function(func): + """Log the start and end of the function. + + It is decorator function. + """ @wraps(func) def wrapper(*args, **kwargs): From dc184408c1aa627cc0501fb268f4b447d9eec6d0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:18:32 +0300 Subject: [PATCH 216/973] add a docstring to the decorator module --- rss_reader/decorator/decorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/decorator/decorator.py b/rss_reader/decorator/decorator.py index cb1efce2..79462db7 100644 --- a/rss_reader/decorator/decorator.py +++ b/rss_reader/decorator/decorator.py @@ -1,3 +1,4 @@ +"""This module contains decorators used in the program.""" from functools import wraps from rss_reader.logger.logger import Logger From ca1607a96f2692a90a789b5109e11595190b31cd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:21:44 +0300 Subject: [PATCH 217/973] import send_log_of_start_function to the loader module --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 8150331f..84b1a6c6 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -4,6 +4,7 @@ from rss_reader.interfaces.icrawler.icrawler import ICrawler from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.logger.logger import Logger +from rss_reader.decorator.decorator import send_log_of_start_function log = Logger.get_logger(__name__) From 6cd3867729a97571648e263b843a8658f674ea95 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:24:51 +0300 Subject: [PATCH 218/973] decorate the get_data method in the loader module --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 84b1a6c6..d8888865 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -35,6 +35,7 @@ def __init__(self, self._crawler = crawler self._parser = parser + @send_log_of_start_function def get_data(self, tag_name: str, title_tag: str, From 33a7d7a0f29273efed6039ec7bb0592455eaeeaf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:29:40 +0300 Subject: [PATCH 219/973] add a docstring to the loader module --- rss_reader/loader/loader.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index d8888865..55553c25 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -1,3 +1,8 @@ +"""This module contains a set of handlers for receiving data. + +The handler receives data from a specific source, selects the necessary +data elements and forms the final result. +""" from rss_reader.interfaces.iloader.iloader import IHandler From 03e0f363d8f37450154bf73e4a38f5cf0a805fde Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:33:00 +0300 Subject: [PATCH 220/973] create the crawler package --- rss_reader/crawler/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/crawler/__init__.py diff --git a/rss_reader/crawler/__init__.py b/rss_reader/crawler/__init__.py new file mode 100644 index 00000000..e69de29b From 7df87b2091f1caf3a5dd89e866867b6cc5e8857d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:33:32 +0300 Subject: [PATCH 221/973] create the crawler module --- rss_reader/crawler/crawler.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/crawler/crawler.py diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py new file mode 100644 index 00000000..e69de29b From c810a365bb6f3d226159a1f3351904976bddfa7e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:34:44 +0300 Subject: [PATCH 222/973] create the SuperCrawler class --- rss_reader/crawler/crawler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index e69de29b..9770c0cc 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -0,0 +1,4 @@ + + +class SuperCrawler(ICrawler): + pass From f021954ce424d81960b8a424c563139ba00c5d0e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:39:48 +0300 Subject: [PATCH 223/973] import ICrawler into the crawler module --- rss_reader/crawler/crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 9770c0cc..5c8ef573 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -1,4 +1,7 @@ +from rss_reader.interfaces.icrawler.icrawler import ICrawler + + class SuperCrawler(ICrawler): pass From 56d1b06da145a1c79502f579637586528a273c6d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:40:48 +0300 Subject: [PATCH 224/973] create the __init__ into the SuperCrawler class --- rss_reader/crawler/crawler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 5c8ef573..95d9d557 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -4,4 +4,5 @@ class SuperCrawler(ICrawler): - pass + def __init__(self, url: str) -> None: + self._url = url From e80f156379828df728cf1eeeb2760cc06970cbe6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:42:22 +0300 Subject: [PATCH 225/973] create the get_data into the SuperCrawler class --- rss_reader/crawler/crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 95d9d557..b0ad8060 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -6,3 +6,6 @@ class SuperCrawler(ICrawler): def __init__(self, url: str) -> None: self._url = url + + def get_data(self) -> bytes: + pass From b29860e16565fbdd310239fd2566fb3fb497e752 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 09:43:00 +0300 Subject: [PATCH 226/973] create the _get_response into the SuperCrawler class --- rss_reader/crawler/crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index b0ad8060..5f8f519f 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -9,3 +9,6 @@ def __init__(self, url: str) -> None: def get_data(self) -> bytes: pass + + def _get_response(self) -> Response: + pass From 8007032498fc1693c80be05ccdcc6410a0b31606 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:07:00 +0300 Subject: [PATCH 227/973] import the Response into the crawler module --- rss_reader/crawler/crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 5f8f519f..fd2eb4fc 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -1,4 +1,6 @@ +from requests import Response + from rss_reader.interfaces.icrawler.icrawler import ICrawler From 08e372e3254d7e2d8b832c9cbab27220c815a538 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:08:35 +0300 Subject: [PATCH 228/973] create the _get_content method in the SuperCrawler --- rss_reader/crawler/crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index fd2eb4fc..0c441f34 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -14,3 +14,6 @@ def get_data(self) -> bytes: def _get_response(self) -> Response: pass + + def _get_content(self, req: Response) -> bytes: + pass From 9de8381c20274e478ce957812174831d0ed4acec Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:08:59 +0300 Subject: [PATCH 229/973] create the _get_status method in the SuperCrawler --- rss_reader/crawler/crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 0c441f34..0314471c 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -17,3 +17,6 @@ def _get_response(self) -> Response: def _get_content(self, req: Response) -> bytes: pass + + def _get_status(self, req: Response) -> int: + pass From a7344d90147b08a86183159da0def514da7a924d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:12:19 +0300 Subject: [PATCH 230/973] get the server's response --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 0314471c..060a63ca 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -13,7 +13,7 @@ def get_data(self) -> bytes: pass def _get_response(self) -> Response: - pass + req = get(self._url) def _get_content(self, req: Response) -> bytes: pass From 23da00b70b55e1c9709329695cc7a425240da798 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:13:10 +0300 Subject: [PATCH 231/973] return the server's response --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 060a63ca..729ef35f 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -14,6 +14,7 @@ def get_data(self) -> bytes: def _get_response(self) -> Response: req = get(self._url) + return req def _get_content(self, req: Response) -> bytes: pass From 080da9adad8670a9bb4f786d12a3a34093ac569a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:13:49 +0300 Subject: [PATCH 232/973] import get into the crawler module --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 729ef35f..612642ac 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -1,5 +1,5 @@ -from requests import Response +from requests import Response, get from rss_reader.interfaces.icrawler.icrawler import ICrawler From 9c8001d83168cfffb069ae1efb62e90d6e457533 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:15:58 +0300 Subject: [PATCH 233/973] create the exceptions module into the crawler package --- rss_reader/crawler/exceptions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/crawler/exceptions.py diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py new file mode 100644 index 00000000..e69de29b From a7c13cc12e1704a8b555861ee6db7a7406b823bb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:16:53 +0300 Subject: [PATCH 234/973] create BadURLError --- rss_reader/crawler/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index e69de29b..2be1e943 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -0,0 +1,4 @@ + + +class BadURLError(Exception): + pass From c8a04d2f903b25cac7215e46e1f7dcd37f2985cf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:17:35 +0300 Subject: [PATCH 235/973] create __init__ into the BadURLError --- rss_reader/crawler/exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 2be1e943..2d363c3a 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -1,4 +1,6 @@ class BadURLError(Exception): - pass + def __init__(self, url, *args, **kwargs) -> None: + self.url = url + super().__init__(*args, **kwargs) From f76717b0e73767de04e2c57c85b97b3092471787 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:19:59 +0300 Subject: [PATCH 236/973] override __str__ method in BadURLError --- rss_reader/crawler/exceptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 2d363c3a..0493c160 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -4,3 +4,6 @@ class BadURLError(Exception): def __init__(self, url, *args, **kwargs) -> None: self.url = url super().__init__(*args, **kwargs) + + def __str__(self) -> str: + return f'It is not possible to get data for the given url ({self.url})' From f7e87dde7f9fac5232a654e956cc59c5e41c2d0c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:37:32 +0300 Subject: [PATCH 237/973] import BadURLError into the crawler module --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 612642ac..a250f6db 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -3,6 +3,7 @@ from rss_reader.interfaces.icrawler.icrawler import ICrawler +from .exceptions import BadURLError class SuperCrawler(ICrawler): From a2e6797d05a416f536c77b760f83cdd4cd3874d9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:47:49 +0300 Subject: [PATCH 238/973] catch the BadURLError in the _get_response method --- rss_reader/crawler/crawler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index a250f6db..597e3ada 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -14,7 +14,10 @@ def get_data(self) -> bytes: pass def _get_response(self) -> Response: - req = get(self._url) + try: + req = get(self._url) + except ConnectionError as e: + raise BadURLError(self._url) from e return req def _get_content(self, req: Response) -> bytes: From fbc403ff7458ea08a0096b68d2400e445a81c0b1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:50:06 +0300 Subject: [PATCH 239/973] add a docstring to the __get_response --- rss_reader/crawler/crawler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 597e3ada..4bbff1bd 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -14,6 +14,12 @@ def get_data(self) -> bytes: pass def _get_response(self) -> Response: + """Get the server's response to an HTTP request. + + :raises BadURLError: If the url is wrong. + :return: Contains a server's response to an HTTP request. + :rtype: Response + """ try: req = get(self._url) except ConnectionError as e: From 5d92135b9f0302936ad28ee0c7691a80e0d4e538 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:52:02 +0300 Subject: [PATCH 240/973] return a request status from SuperCrawler --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 4bbff1bd..8040cf1a 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -30,4 +30,4 @@ def _get_content(self, req: Response) -> bytes: pass def _get_status(self, req: Response) -> int: - pass + return req.status_code From 199f379b980f285c67c95a828b4bcc43f32d895b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:52:54 +0300 Subject: [PATCH 241/973] add a docstring to the _get_status --- rss_reader/crawler/crawler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 8040cf1a..f413d707 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -30,4 +30,11 @@ def _get_content(self, req: Response) -> bytes: pass def _get_status(self, req: Response) -> int: + """Get status code. + + :param req: Contains a server's response. + :type req: Response + :return: Status code. + :rtype: int + """ return req.status_code From 37930dce2fd55992c73c1cf4c4797640eb22c915 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:53:40 +0300 Subject: [PATCH 242/973] return a content from SuperCrawler --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index f413d707..13698f8a 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -27,7 +27,7 @@ def _get_response(self) -> Response: return req def _get_content(self, req: Response) -> bytes: - pass + return req.content def _get_status(self, req: Response) -> int: """Get status code. From 345d005dcdf20f7a0965bb3e0fb9448a46fccc42 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:54:38 +0300 Subject: [PATCH 243/973] add a docstrint to the _get_content --- rss_reader/crawler/crawler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 13698f8a..3787014b 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -27,6 +27,13 @@ def _get_response(self) -> Response: return req def _get_content(self, req: Response) -> bytes: + """Get the content of the Response object. + + :param req: Contains a server's response. + :type req: Response + :return: Content of the response, in bytes. + :rtype: bytes + """ return req.content def _get_status(self, req: Response) -> int: From e1401102072eb1497d9f4757d02736dabc3c274b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:55:46 +0300 Subject: [PATCH 244/973] call a _get_respons in get_data --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 3787014b..2e2d2646 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -11,7 +11,7 @@ def __init__(self, url: str) -> None: self._url = url def get_data(self) -> bytes: - pass + r = self._get_response() def _get_response(self) -> Response: """Get the server's response to an HTTP request. From 625eb264f8ba31bb98602942ea78ddc702782088 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:57:01 +0300 Subject: [PATCH 245/973] call _get_status in get_data --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 2e2d2646..04daea9f 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -12,6 +12,7 @@ def __init__(self, url: str) -> None: def get_data(self) -> bytes: r = self._get_response() + status = self._get_status(r) def _get_response(self) -> Response: """Get the server's response to an HTTP request. From fb855bee9a8483a066903c86f262341bb613c185 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:58:20 +0300 Subject: [PATCH 246/973] return the requested content from the SuperCrawler --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 04daea9f..4752b82b 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -13,6 +13,7 @@ def __init__(self, url: str) -> None: def get_data(self) -> bytes: r = self._get_response() status = self._get_status(r) + return self._get_content(r) def _get_response(self) -> Response: """Get the server's response to an HTTP request. From 45cce4a2e28eb991024f668b6956bd5d3f6ba986 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 10:59:33 +0300 Subject: [PATCH 247/973] create FailStatusCodeError --- rss_reader/crawler/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 0493c160..ed36090b 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -7,3 +7,7 @@ def __init__(self, url, *args, **kwargs) -> None: def __str__(self) -> str: return f'It is not possible to get data for the given url ({self.url})' + + +class FailStatusCodeError(Exception): + pass From 4762a136e6e7864007294a7238e40cac8a1100a4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:00:42 +0300 Subject: [PATCH 248/973] override __init__ in the FailStatusCodeError --- rss_reader/crawler/exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index ed36090b..289ac3be 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -10,4 +10,6 @@ def __str__(self) -> str: class FailStatusCodeError(Exception): - pass + def __init__(self, status_code, *args, **kwargs) -> None: + self.status_code = status_code + super().__init__(*args, **kwargs) From 7ad7febd9268e6fca1e6c4c99a386744cc3b4051 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:04:11 +0300 Subject: [PATCH 249/973] override __str__ in the FailStatusCodeError --- rss_reader/crawler/exceptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 289ac3be..2c7bc01f 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -13,3 +13,6 @@ class FailStatusCodeError(Exception): def __init__(self, status_code, *args, **kwargs) -> None: self.status_code = status_code super().__init__(*args, **kwargs) + + def __str__(self) -> str: + return 'An unsupported HTTP status code returned. ({self.status_code})' \ No newline at end of file From 4f2458769de84285e016239a68ee328e6c1b3070 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:05:28 +0300 Subject: [PATCH 250/973] add a docstring to the FailStatusCodeError --- rss_reader/crawler/exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 2c7bc01f..ff12519f 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -10,9 +10,11 @@ def __str__(self) -> str: class FailStatusCodeError(Exception): + """Occurs when the server response code differs from the expected one.""" + def __init__(self, status_code, *args, **kwargs) -> None: self.status_code = status_code super().__init__(*args, **kwargs) def __str__(self) -> str: - return 'An unsupported HTTP status code returned. ({self.status_code})' \ No newline at end of file + return 'An unsupported HTTP status code returned. ({self.status_code})' From d5fee51a03a4378e7abfaef635782e07c9a14f2b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:08:22 +0300 Subject: [PATCH 251/973] add a docstring to the FailStatusCodeError --- rss_reader/crawler/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index ff12519f..35d184be 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -1,6 +1,8 @@ class BadURLError(Exception): + """"Occurs when the url is specified incorrectly.""" + def __init__(self, url, *args, **kwargs) -> None: self.url = url super().__init__(*args, **kwargs) From 3c764a5ec8548c74d72398cb49393e954e197e9c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:09:25 +0300 Subject: [PATCH 252/973] add a docstring to the ecxeptions module --- rss_reader/crawler/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 35d184be..085c4d6c 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -1,3 +1,4 @@ +"""This module contains custom exceptions for the crawler package.""" class BadURLError(Exception): From a1e1b7d1ff468f39354c25dad8416da3e1fce5b9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:49:43 +0300 Subject: [PATCH 253/973] check return status in get_data --- rss_reader/crawler/crawler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 4752b82b..e20e1708 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -13,7 +13,9 @@ def __init__(self, url: str) -> None: def get_data(self) -> bytes: r = self._get_response() status = self._get_status(r) - return self._get_content(r) + if status == 200: + return self._get_content(r) + raise FailStatusCodeError(status) def _get_response(self) -> Response: """Get the server's response to an HTTP request. From cb312568dca277c8ac0808ebd4477af3f6bad7d1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:50:39 +0300 Subject: [PATCH 254/973] import FailStatusCodeError in the crawler module --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index e20e1708..e9ec24be 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -3,7 +3,7 @@ from rss_reader.interfaces.icrawler.icrawler import ICrawler -from .exceptions import BadURLError +from .exceptions import BadURLError, FailStatusCodeError class SuperCrawler(ICrawler): From aef5c115d2d1a66ce17f90388312a84fd4dd52a5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:52:46 +0300 Subject: [PATCH 255/973] import ConnectionError in the crawler module --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index e9ec24be..9a03396f 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -1,5 +1,5 @@ -from requests import Response, get +from requests import Response, get, ConnectionError from rss_reader.interfaces.icrawler.icrawler import ICrawler From 4b1ade340a89abf39344ff15d2c416e65ee73802 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:53:53 +0300 Subject: [PATCH 256/973] import send_log_of_start_function in the crawler module --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 9a03396f..e9d1845d 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -3,6 +3,7 @@ from rss_reader.interfaces.icrawler.icrawler import ICrawler +from rss_reader.decorator.decorator import send_log_of_start_function from .exceptions import BadURLError, FailStatusCodeError From c977dde3a806d37f841a4712b017154ca437abb5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:54:54 +0300 Subject: [PATCH 257/973] decorate the get_data method in the SuperCrawler --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index e9d1845d..db23406a 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -11,6 +11,7 @@ class SuperCrawler(ICrawler): def __init__(self, url: str) -> None: self._url = url + @send_log_of_start_function def get_data(self) -> bytes: r = self._get_response() status = self._get_status(r) From 09a1b98281a5a8a07365c7e8c90dac140f272cba Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:56:09 +0300 Subject: [PATCH 258/973] add a docstring to the get_data --- rss_reader/crawler/crawler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index db23406a..a785e87e 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -13,6 +13,13 @@ def __init__(self, url: str) -> None: @send_log_of_start_function def get_data(self) -> bytes: + """Get the content of the requested page. + + :raises FailStatusCodeError: If status code is not equal to 200. + :return: Page data. + :rtype: bytes + """ + r = self._get_response() status = self._get_status(r) if status == 200: From a37e509af99358630f1e70ad665e7e762a277972 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 11:56:46 +0300 Subject: [PATCH 259/973] add a docstring to the __init__ --- rss_reader/crawler/crawler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index a785e87e..0a80c6ab 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -9,6 +9,11 @@ class SuperCrawler(ICrawler): def __init__(self, url: str) -> None: + """Initializer. + + :param url: URL of the requested web page. + :type url: str + """ self._url = url @send_log_of_start_function From 6483bdf646d92290f58cdc1d3e16c264ef6e1923 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:07:46 +0300 Subject: [PATCH 260/973] add a docstring to the SuperCrawler class --- rss_reader/crawler/crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 0a80c6ab..4b91f57b 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -8,6 +8,8 @@ class SuperCrawler(ICrawler): + """A class to represent a crawler.""" + def __init__(self, url: str) -> None: """Initializer. From 190312d9f3088a2c3bf006653c6f78f005c12cce Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:08:16 +0300 Subject: [PATCH 261/973] add a docstring to the crawler module --- rss_reader/crawler/crawler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 4b91f57b..8aeb383c 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -1,6 +1,7 @@ +"""This module contains objects that receive data from the internet.""" -from requests import Response, get, ConnectionError +from requests import Response, get, ConnectionError from rss_reader.interfaces.icrawler.icrawler import ICrawler from rss_reader.decorator.decorator import send_log_of_start_function From ee4feda4e4c04a8399d3d994b9380d119f7f3939 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:12:17 +0300 Subject: [PATCH 262/973] create the parser package --- rss_reader/parser/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/parser/__init__.py diff --git a/rss_reader/parser/__init__.py b/rss_reader/parser/__init__.py new file mode 100644 index 00000000..e69de29b From 0320a3b0c017122430bc443966b200b76a8a6c00 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:13:33 +0300 Subject: [PATCH 263/973] create the parser module --- rss_reader/parser/parser.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/parser/parser.py diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py new file mode 100644 index 00000000..e69de29b From 21346ffef43a11bb9de84c74822adccce3591820 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:13:59 +0300 Subject: [PATCH 264/973] create the BeautifulParser class --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index e69de29b..96a0b749 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -0,0 +1,3 @@ + +class BeautifulParser(IParser): + pass From 2868bb3ff38662c625db8c9b3d6fd5a824818ebe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:16:02 +0300 Subject: [PATCH 265/973] import the IParser into the parser module --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 96a0b749..75a8a538 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,3 +1,6 @@ +from rss_reader.interfaces.iparser.iparser import IParser + + class BeautifulParser(IParser): pass From c9dbf494ad8030d8355f171a5cf1996db54e772f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:20:15 +0300 Subject: [PATCH 266/973] create the __init__ method in the BeautifulParser --- rss_reader/parser/parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 75a8a538..b71c5e6d 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -3,4 +3,6 @@ class BeautifulParser(IParser): - pass + + def __init__(self, subsystem: ISubsystem) -> None: + self._subsystem = subsystem From a1ab8c80b22fd0680f7f37fe06b7420d0319ceb2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:22:35 +0300 Subject: [PATCH 267/973] create the isubsystem module --- rss_reader/interfaces/iparser/isubsystem.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iparser/isubsystem.py diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py new file mode 100644 index 00000000..e69de29b From 54f92cfdb2fd34763dd109dddb8f5025756f201c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:23:00 +0300 Subject: [PATCH 268/973] create the ISubsystem class --- rss_reader/interfaces/iparser/isubsystem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index e69de29b..7cb4b2fd 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -0,0 +1,4 @@ + + +class ISubsystem(ABC): + pass \ No newline at end of file From 9195deb9f40f08d0a520a9fe8be45bef4db3bb66 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:23:43 +0300 Subject: [PATCH 269/973] import ABC into the isubsystem module --- rss_reader/interfaces/iparser/isubsystem.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index 7cb4b2fd..2714a502 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -1,4 +1,6 @@ +from abc import ABC + class ISubsystem(ABC): - pass \ No newline at end of file + pass From 8372402b70eda0d1dc5e265b88684d9217f4a85a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:24:01 +0300 Subject: [PATCH 270/973] import abstractmethod into the isubsystem module --- rss_reader/interfaces/iparser/isubsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index 2714a502..13167d2b 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -1,5 +1,5 @@ -from abc import ABC +from abc import ABC, abstractmethod class ISubsystem(ABC): From 630af657af41c3c71a8343a0a062dd99d17ed996 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:24:56 +0300 Subject: [PATCH 271/973] create the find_all method in the ISubsystem --- rss_reader/interfaces/iparser/isubsystem.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index 13167d2b..fa75bad0 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -3,4 +3,6 @@ class ISubsystem(ABC): - pass + + def find_all(self, name: str = None, limit: int = None) -> Iterable: + pass From ae68131dd80ca45473bfce9e4de4fc486dfb009e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:25:27 +0300 Subject: [PATCH 272/973] import Iterable into the isubsystem module --- rss_reader/interfaces/iparser/isubsystem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index fa75bad0..e0293c3c 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod +from typing import Iterable class ISubsystem(ABC): From 86ff1bdca4b904b66a8865031d39b8d837da9a07 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:25:52 +0300 Subject: [PATCH 273/973] create the select method in the ISubsystem --- rss_reader/interfaces/iparser/isubsystem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index e0293c3c..e1b1a766 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -7,3 +7,6 @@ class ISubsystem(ABC): def find_all(self, name: str = None, limit: int = None) -> Iterable: pass + + def select(self, selector: str, limit: int = None) -> Iterable: + pass From 5c7067990158d6026bdb32eb9a0d6b6c5e71ba62 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:30:49 +0300 Subject: [PATCH 274/973] add a dockstring to the find_all method --- rss_reader/interfaces/iparser/isubsystem.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index e1b1a766..97e6651a 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -6,6 +6,16 @@ class ISubsystem(ABC): def find_all(self, name: str = None, limit: int = None) -> Iterable: + """Return all tags with the given name. + + :param name: A filter on tag name, defaults to None + :type name: str, optional + :param limit: Stop looking after finding this many results, + defaults to None. + :type limit: int, optional + :return: An iterable object that provides the found elements. + :rtype: Iterable + """ pass def select(self, selector: str, limit: int = None) -> Iterable: From f07ed05a116fa01411f6d12c888e9bf8824571f4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:32:51 +0300 Subject: [PATCH 275/973] add a dockstring to the select method --- rss_reader/interfaces/iparser/isubsystem.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index 97e6651a..6f9cdcf1 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -19,4 +19,14 @@ def find_all(self, name: str = None, limit: int = None) -> Iterable: pass def select(self, selector: str, limit: int = None) -> Iterable: + """Return tags selected by CSS selector. + + :param selector: A string containing a CSS selector. + :type selector: str + :param limit: After finding this number of results, stop looking, + defaults to None + :type limit: int, optional + :return: An iterable object that provides the found elements. + :rtype: Iterable + """ pass From 98331278665f79f0c8f96d217a454af813ae3329 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:33:52 +0300 Subject: [PATCH 276/973] add a dockstring to the ISubsystem class --- rss_reader/interfaces/iparser/isubsystem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index 6f9cdcf1..c4032c6b 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -4,6 +4,7 @@ class ISubsystem(ABC): + """Basic interface of a third-party data parser.""" def find_all(self, name: str = None, limit: int = None) -> Iterable: """Return all tags with the given name. From 422a4b7768b15f4bbe093faca9f8c7ca42831375 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:36:29 +0300 Subject: [PATCH 277/973] add a dockstring to the isubsystem module --- rss_reader/interfaces/iparser/isubsystem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index c4032c6b..0b4ce8c3 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -1,3 +1,5 @@ +"""This module contains a set of interfaces that describe the work third party parsers.""" + from abc import ABC, abstractmethod from typing import Iterable From c98ee46bc1e7d28d29792fdb37bda9f499f6f69a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:37:57 +0300 Subject: [PATCH 278/973] import ISubsystem into the parser module --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index b71c5e6d..17d30529 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,5 +1,6 @@ from rss_reader.interfaces.iparser.iparser import IParser +from rss_reader.interfaces.iparser.isubsystem import ISubsystem class BeautifulParser(IParser): From 6d6bda95e90a24786c8df01c89c7750a70fccfde Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:42:28 +0300 Subject: [PATCH 279/973] add a docstrint to the __init__ in the BeautifulParser --- rss_reader/parser/parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 17d30529..2e15385e 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -6,4 +6,9 @@ class BeautifulParser(IParser): def __init__(self, subsystem: ISubsystem) -> None: + """Initializer. + + :param subsystem: Third party parser. + :type subsystem: ISubsystem + """ self._subsystem = subsystem From 6f72faed4b17988f173f971dffe8ef1c18cdf8ad Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:47:46 +0300 Subject: [PATCH 280/973] decorated the functions in the class ISubsystem --- rss_reader/interfaces/iparser/isubsystem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iparser/isubsystem.py b/rss_reader/interfaces/iparser/isubsystem.py index 0b4ce8c3..b9c304c1 100644 --- a/rss_reader/interfaces/iparser/isubsystem.py +++ b/rss_reader/interfaces/iparser/isubsystem.py @@ -8,6 +8,7 @@ class ISubsystem(ABC): """Basic interface of a third-party data parser.""" + @abstractmethod def find_all(self, name: str = None, limit: int = None) -> Iterable: """Return all tags with the given name. @@ -21,6 +22,7 @@ def find_all(self, name: str = None, limit: int = None) -> Iterable: """ pass + @abstractmethod def select(self, selector: str, limit: int = None) -> Iterable: """Return tags selected by CSS selector. From 9f6d248f82a160b6a40609ec5f026fb0d6b816f5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:50:13 +0300 Subject: [PATCH 281/973] create the create_parser method in the BeautifulParser --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 2e15385e..75c6baa0 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -12,3 +12,6 @@ def __init__(self, subsystem: ISubsystem) -> None: :type subsystem: ISubsystem """ self._subsystem = subsystem + + def create_parser(self, markup: bytes, features: str = 'xml') -> None: + self._subsystem = self._subsystem(markup, features) From a13b4edb7510bcb32c510670befef5db7d45cef6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 12:52:47 +0300 Subject: [PATCH 282/973] add a docstring into the create_parser method --- rss_reader/parser/parser.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 75c6baa0..4fe233f1 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -14,4 +14,16 @@ def __init__(self, subsystem: ISubsystem) -> None: self._subsystem = subsystem def create_parser(self, markup: bytes, features: str = 'xml') -> None: + """Create a parser object. + + :param markup: A string or a file-like object representing markup + to be parsed. + :type markup: bytes + :param features: Desirable features of the parser to be used. This may + be the name of a specific parser ("lxml", "lxml-xml", + "html.parser", or "html5lib") or it may be the type + of markup to be used ("html", "html5", "xml"). + Defaults to 'xml'. + :type features: str, optional + """ self._subsystem = self._subsystem(markup, features) From 9f461864b12283b18d20b4ac473c23e5754204fc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 13:02:04 +0300 Subject: [PATCH 283/973] add @send_log_of_start_function to the create_parser --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 4fe233f1..ac487179 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,6 +1,7 @@ from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.interfaces.iparser.isubsystem import ISubsystem +from rss_reader.decorator.decorator import send_log_of_start_function class BeautifulParser(IParser): @@ -13,6 +14,7 @@ def __init__(self, subsystem: ISubsystem) -> None: """ self._subsystem = subsystem + @send_log_of_start_function def create_parser(self, markup: bytes, features: str = 'xml') -> None: """Create a parser object. From 75ce14ce30e3ef3d9a3cf3dbc632d0057050c504 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 13:03:53 +0300 Subject: [PATCH 284/973] create the _find_all method --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index ac487179..c0cb4d0c 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -29,3 +29,6 @@ def create_parser(self, markup: bytes, features: str = 'xml') -> None: :type features: str, optional """ self._subsystem = self._subsystem(markup, features) + + def _find_all(self, name: str = None, limit_elms: int = None) -> Iterable: + return self._subsystem.find_all(name, limit=limit_elms) From f3c6c1b2b2b0c0eaad35313c50e45eee09992c66 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 13:06:34 +0300 Subject: [PATCH 285/973] add a docstring to the _find_all --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index c0cb4d0c..1f3c765c 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -31,4 +31,5 @@ def create_parser(self, markup: bytes, features: str = 'xml') -> None: self._subsystem = self._subsystem(markup, features) def _find_all(self, name: str = None, limit_elms: int = None) -> Iterable: + """Return all tags with the given name.""" return self._subsystem.find_all(name, limit=limit_elms) From 93e8fea54221333af66fe42cfc5df1a7a0e5c622 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 13:07:33 +0300 Subject: [PATCH 286/973] import Iterable to the parser module --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 1f3c765c..9eb0d2c8 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,4 +1,6 @@ +from typing import Iterable + from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.interfaces.iparser.isubsystem import ISubsystem from rss_reader.decorator.decorator import send_log_of_start_function From e5dfbdbd5477a41a3baf5e8019642e2c27f75c4b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 13:08:41 +0300 Subject: [PATCH 287/973] create the _select method in the BeautifulParser class --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 9eb0d2c8..cefbd6d3 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -35,3 +35,6 @@ def create_parser(self, markup: bytes, features: str = 'xml') -> None: def _find_all(self, name: str = None, limit_elms: int = None) -> Iterable: """Return all tags with the given name.""" return self._subsystem.find_all(name, limit=limit_elms) + + def _select(self, selector: str, limit_elms: int = None) -> Iterable: + return self._subsystem.select(selector, limit=limit_elms) From 3d90e4301bc41e19266ca286c05138c46c848022 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 13:09:34 +0300 Subject: [PATCH 288/973] add a docstring to the _select --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index cefbd6d3..44c63e7e 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -37,4 +37,5 @@ def _find_all(self, name: str = None, limit_elms: int = None) -> Iterable: return self._subsystem.find_all(name, limit=limit_elms) def _select(self, selector: str, limit_elms: int = None) -> Iterable: + """Return tags selected by CSS selector.""" return self._subsystem.select(selector, limit=limit_elms) From b5e0b7a243afb9141beecaf7fffd180843e16719 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:14:57 +0300 Subject: [PATCH 289/973] create get_tags_text method in the BeautifulParser --- rss_reader/parser/parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 44c63e7e..aa88b0cc 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -39,3 +39,8 @@ def _find_all(self, name: str = None, limit_elms: int = None) -> Iterable: def _select(self, selector: str, limit_elms: int = None) -> Iterable: """Return tags selected by CSS selector.""" return self._subsystem.select(selector, limit=limit_elms) + + def get_tags_text(self, + selector: str, + limit_elms: int = None) -> Generator[str, None, None]: + pass From 5899d80761850732b4dd5509323bc02d25b9c050 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:15:39 +0300 Subject: [PATCH 290/973] import Generator in the parser module --- rss_reader/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index aa88b0cc..2ef7f4e7 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,5 +1,5 @@ -from typing import Iterable +from typing import Generator, Iterable from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.interfaces.iparser.isubsystem import ISubsystem From dbf57b4f365c6b0f880a73d6c4fb791e12c77db0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:18:51 +0300 Subject: [PATCH 291/973] get tags by selector --- rss_reader/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 2ef7f4e7..3c630e23 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -43,4 +43,4 @@ def _select(self, selector: str, limit_elms: int = None) -> Iterable: def get_tags_text(self, selector: str, limit_elms: int = None) -> Generator[str, None, None]: - pass + tags = self._select(selector, limit_elms) From aab991318b53c913a6fad8fcef9073d60ff44b94 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:26:37 +0300 Subject: [PATCH 292/973] if there are no tags then throw an EmptyListError exception --- rss_reader/parser/parser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 3c630e23..884892b5 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -43,4 +43,8 @@ def _select(self, selector: str, limit_elms: int = None) -> Iterable: def get_tags_text(self, selector: str, limit_elms: int = None) -> Generator[str, None, None]: - tags = self._select(selector, limit_elms) + tags = self._select(selector, limit_elms) + + if not tags: + raise EmptyListError( + "No matching tags. Maybe the selector is wrong.") From 670ba94be61f077985c2a0ff803b026fad74d177 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:27:18 +0300 Subject: [PATCH 293/973] create exceptions.py --- rss_reader/parser/exceptions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/parser/exceptions.py diff --git a/rss_reader/parser/exceptions.py b/rss_reader/parser/exceptions.py new file mode 100644 index 00000000..e69de29b From 51180d094a7681452f3a06412932b06136a7dbb1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:28:06 +0300 Subject: [PATCH 294/973] create EmptyListError class --- rss_reader/parser/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/parser/exceptions.py b/rss_reader/parser/exceptions.py index e69de29b..c07ee521 100644 --- a/rss_reader/parser/exceptions.py +++ b/rss_reader/parser/exceptions.py @@ -0,0 +1,4 @@ + + +class EmptyListError(Exception): + pass \ No newline at end of file From b43a29d5f13ba0b6531176661c0d65a8ba2b9029 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:28:44 +0300 Subject: [PATCH 295/973] add a dockstring to the EmptyListError --- rss_reader/parser/exceptions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/parser/exceptions.py b/rss_reader/parser/exceptions.py index c07ee521..8fe97cf3 100644 --- a/rss_reader/parser/exceptions.py +++ b/rss_reader/parser/exceptions.py @@ -1,4 +1,5 @@ class EmptyListError(Exception): - pass \ No newline at end of file + """List has no elements.""" + pass From 3f7ce5893d41b503287e2c36548b870f1db84841 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:29:49 +0300 Subject: [PATCH 296/973] add a docstring to the exceptions module --- rss_reader/parser/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/exceptions.py b/rss_reader/parser/exceptions.py index 8fe97cf3..c449781c 100644 --- a/rss_reader/parser/exceptions.py +++ b/rss_reader/parser/exceptions.py @@ -1,3 +1,4 @@ +"""This module contains custom exceptions for the parser package.""" class EmptyListError(Exception): From 8c44a74756548febbde916e138227603d260e996 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:31:07 +0300 Subject: [PATCH 297/973] import EmptyListError into the parser module --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 884892b5..82825eb3 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -4,6 +4,7 @@ from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.interfaces.iparser.isubsystem import ISubsystem from rss_reader.decorator.decorator import send_log_of_start_function +from .exceptions import EmptyListError class BeautifulParser(IParser): From 22ee9223acdef06f057750806bceefaa5086904e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:32:28 +0300 Subject: [PATCH 298/973] log the EmptyListError error --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 82825eb3..2ade6d58 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -47,5 +47,6 @@ def get_tags_text(self, tags = self._select(selector, limit_elms) if not tags: + log.exception("No matching tags. Maybe the selector is wrong.") raise EmptyListError( "No matching tags. Maybe the selector is wrong.") From d776b83256c220a5dd184eed61d95a5b8b241238 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:33:39 +0300 Subject: [PATCH 299/973] impoth the Logger into the parser module --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 2ade6d58..b74f848d 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,6 +1,7 @@ from typing import Generator, Iterable +from rss_reader.logger.logger import Logger from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.interfaces.iparser.isubsystem import ISubsystem from rss_reader.decorator.decorator import send_log_of_start_function From e8dc3efc03326afbbc5c54d5746e93fb080c1435 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:34:20 +0300 Subject: [PATCH 300/973] get logger yhe program --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index b74f848d..bed71ac7 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -7,6 +7,8 @@ from rss_reader.decorator.decorator import send_log_of_start_function from .exceptions import EmptyListError +log = Logger.get_logger(__name__) + class BeautifulParser(IParser): From e01a295926b8adefb0b940a1b7241346470b7215 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:35:47 +0300 Subject: [PATCH 301/973] return the Generator grom the get_tags_text method --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index bed71ac7..564ff9ab 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -53,3 +53,6 @@ def get_tags_text(self, log.exception("No matching tags. Maybe the selector is wrong.") raise EmptyListError( "No matching tags. Maybe the selector is wrong.") + + for i in tags: + yield i.text From 69c114886773a33400e2cab293799c49e3f361ef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:37:12 +0300 Subject: [PATCH 302/973] add the @send_log_of_start_function to the get_tags_text --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 564ff9ab..729fe05c 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -44,6 +44,7 @@ def _select(self, selector: str, limit_elms: int = None) -> Iterable: """Return tags selected by CSS selector.""" return self._subsystem.select(selector, limit=limit_elms) + @send_log_of_start_function def get_tags_text(self, selector: str, limit_elms: int = None) -> Generator[str, None, None]: From af7a5404fbc5b58043d1c2bd58a105806b943418 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:38:41 +0300 Subject: [PATCH 303/973] add a docstring to the get_tags_text in the parser module --- rss_reader/parser/parser.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 729fe05c..13bfe476 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -48,6 +48,16 @@ def _select(self, selector: str, limit_elms: int = None) -> Iterable: def get_tags_text(self, selector: str, limit_elms: int = None) -> Generator[str, None, None]: + """Returns the text stored in the tag(s). + + :param selector: A string containing a CSS selector. + :type selector: str + :param limit_elms: The number of elements to return, defaults to None. + :type limit_elms: int, optional + :yield: Returns the text of each element. + :rtype: Generator[str, None, None] + """ + tags = self._select(selector, limit_elms) if not tags: From e59dd29f387636a7786f2efd82c32fa649f8be77 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:40:02 +0300 Subject: [PATCH 304/973] create a get_items method in the BeautifulParser class --- rss_reader/parser/parser.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 13bfe476..cc7cb6f2 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -67,3 +67,9 @@ def get_tags_text(self, for i in tags: yield i.text + + def get_items(self, + template: dict, + name: str, + limit_elms: int = None) -> List[dict]: + pass From 2c31789f9ed650aa27d4278e3c75a43ff96bcf43 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:42:06 +0300 Subject: [PATCH 305/973] import the List type in the parser module --- rss_reader/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index cc7cb6f2..fb9ca2b1 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,5 +1,5 @@ -from typing import Generator, Iterable +from typing import Generator, Iterable, List from rss_reader.logger.logger import Logger from rss_reader.interfaces.iparser.iparser import IParser From 7d0fd8ffd07e0f084fe03223dbc64a1f58c42c26 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:43:11 +0300 Subject: [PATCH 306/973] declare the final_dict variable --- rss_reader/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index fb9ca2b1..c5ad79b9 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -72,4 +72,4 @@ def get_items(self, template: dict, name: str, limit_elms: int = None) -> List[dict]: - pass + final_dict = [] From 178ac16b472775e16264e401ac5a27fe43a47090 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:43:59 +0300 Subject: [PATCH 307/973] find all tags with the given name --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index c5ad79b9..0b1c6523 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -73,3 +73,4 @@ def get_items(self, name: str, limit_elms: int = None) -> List[dict]: final_dict = [] + items = self._find_all(name, limit_elms) From 5b6044d7d017697671918a23040959727f350b42 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 18:50:02 +0300 Subject: [PATCH 308/973] start iterating over items --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 0b1c6523..fc60170c 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -74,3 +74,5 @@ def get_items(self, limit_elms: int = None) -> List[dict]: final_dict = [] items = self._find_all(name, limit_elms) + + for item in items: From c0d1d3d2564845b90106c7c5768801c5337de782 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:00:54 +0300 Subject: [PATCH 309/973] create the item_dict variable --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index fc60170c..cc2481f1 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -76,3 +76,6 @@ def get_items(self, items = self._find_all(name, limit_elms) for item in items: + + # The dictionary stores information about a particular news item. + item_dict = {} From 26f408dc161a3c051506731a414a3c7fd9b54a3d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:02:28 +0300 Subject: [PATCH 310/973] find all tags inside the tag that stores the news --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index cc2481f1..7002ce0a 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -79,3 +79,5 @@ def get_items(self, # The dictionary stores information about a particular news item. item_dict = {} + # find all tags inside the tag that stores the news + item_tags = item.find_all() From bef8e8c59c75cda5b18ac84cfc6d73ced211544e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:03:25 +0300 Subject: [PATCH 311/973] start iterating over subtags --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 7002ce0a..90d42029 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -81,3 +81,4 @@ def get_items(self, item_dict = {} # find all tags inside the tag that stores the news item_tags = item.find_all() + for tag in item_tags: From d574e09703df41ad56ad63575ed33db35c853579 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:04:17 +0300 Subject: [PATCH 312/973] get the name of the subtag --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 90d42029..c242fe69 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -82,3 +82,4 @@ def get_items(self, # find all tags inside the tag that stores the news item_tags = item.find_all() for tag in item_tags: + tag_name = tag.name From 29446461526e7172cb077086a41a36219b904665 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:08:51 +0300 Subject: [PATCH 313/973] write the contents to the dictionary --- rss_reader/parser/parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index c242fe69..f2e131ca 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -83,3 +83,8 @@ def get_items(self, item_tags = item.find_all() for tag in item_tags: tag_name = tag.name + # if the tag name is contained in the template, + # then we write its contents to the dictionary. + if tag_name in template: + if isinstance(template[tag_name], str): + item_dict[tag_name] = tag.text From cf90c790a49b6ddfa07141b981b04437a3f49c5f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:11:04 +0300 Subject: [PATCH 314/973] create a dictionary with subtag attribute data --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index f2e131ca..37f98322 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -88,3 +88,6 @@ def get_items(self, if tag_name in template: if isinstance(template[tag_name], str): item_dict[tag_name] = tag.text + else: + # A dictionary with subtag attribute data + attrs_dict = {} From 99be7a205035c2c26342c8a19e5c949b2c9617d1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:12:43 +0300 Subject: [PATCH 315/973] get tag attributes from template --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 37f98322..dcf80c92 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -91,3 +91,5 @@ def get_items(self, else: # A dictionary with subtag attribute data attrs_dict = {} + # The tag attributes from the template to find + attrs = template[tag_name] From 5cba0cc5ffcf3829c5bc46442dafed5bcd9584ed Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:13:44 +0300 Subject: [PATCH 316/973] iterate over each attribute --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index dcf80c92..5a80b921 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -93,3 +93,4 @@ def get_items(self, attrs_dict = {} # The tag attributes from the template to find attrs = template[tag_name] + for attr in attrs: From d449128606566ce680eadf6f6e3049f321131daf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 19:16:18 +0300 Subject: [PATCH 317/973] get the value of each attribute --- rss_reader/parser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 5a80b921..ab49d715 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -94,3 +94,6 @@ def get_items(self, # The tag attributes from the template to find attrs = template[tag_name] for attr in attrs: + # Write down the value of each attribute + # if present or None + attrs_dict.update({attr: tag.get(attr, None)}) From 5936054ab81abaf79c767e8adab48a59c92783c8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:20:21 +0300 Subject: [PATCH 318/973] store attributes and their values in a dictionary --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index ab49d715..c2e9ab18 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -97,3 +97,4 @@ def get_items(self, # Write down the value of each attribute # if present or None attrs_dict.update({attr: tag.get(attr, None)}) + item_dict[tag_name] = attrs_dict From 25bc7c58a4d5819085c09c0b67d7b6371339e56d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:21:49 +0300 Subject: [PATCH 319/973] save each news item in a dictionary --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index c2e9ab18..4309ce1a 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -98,3 +98,4 @@ def get_items(self, # if present or None attrs_dict.update({attr: tag.get(attr, None)}) item_dict[tag_name] = attrs_dict + final_dict.append(item_dict) From ce18945e9dbf586ba660d62fd09b924dee1192cc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:22:41 +0300 Subject: [PATCH 320/973] return dictionary with received news --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 4309ce1a..850e3966 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -99,3 +99,4 @@ def get_items(self, attrs_dict.update({attr: tag.get(attr, None)}) item_dict[tag_name] = attrs_dict final_dict.append(item_dict) + return final_dict From be7c7ef3e3e15db2a72dcc4e92270193daf7e824 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:26:33 +0300 Subject: [PATCH 321/973] add a dockstring to the get_items in the BeautifulParser --- rss_reader/parser/parser.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 850e3966..9ff95f00 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -72,6 +72,35 @@ def get_items(self, template: dict, name: str, limit_elms: int = None) -> List[dict]: + """Get a list of found items. + + :param template: Specifies the element search pattern. Represents a + dictionary. The keys of the dictionary are the tags + to be found, and the value is just a string + (for example, 'text'). If you need to find the + attributes of a tag, then the value is a list that + lists all the attributes you need to search + (for example, Date: Fri, 24 Jun 2022 20:27:37 +0300 Subject: [PATCH 322/973] add @send_log_of_start_function to the get_items --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 9ff95f00..1ba6b1f4 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -68,6 +68,7 @@ def get_tags_text(self, for i in tags: yield i.text + @send_log_of_start_function def get_items(self, template: dict, name: str, From 134a71c2584f2930bdfbc3d37346ccd2e5c93732 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:29:12 +0300 Subject: [PATCH 323/973] add a dosctring to the BeautifulParser class --- rss_reader/parser/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 1ba6b1f4..324c6bff 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -11,6 +11,7 @@ class BeautifulParser(IParser): + """A class to represent a parser.""" def __init__(self, subsystem: ISubsystem) -> None: """Initializer. From fdb05048a1d130effd75aae7a82a7a43107d96a1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:30:38 +0300 Subject: [PATCH 324/973] add a docstring to the parser module --- rss_reader/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index 324c6bff..a1c7f618 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -1,3 +1,5 @@ +"""This module contains a class for parsing data received from the net.""" + from typing import Generator, Iterable, List From 6ffc3884e15515285f55560e72854104918196af Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:35:55 +0300 Subject: [PATCH 325/973] import the IHandler into the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index eaa92e75..06c5facd 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -3,6 +3,7 @@ from typing import Dict from rss_reader.logger.logger import Logger +from rss_reader.interfaces.iloader.iloader import IHandler from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) From 12f56975b278eeb4870dbe5dc7f28f9183547836 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:37:19 +0300 Subject: [PATCH 326/973] import the FromWebHandler into the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 06c5facd..b5f2c178 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -4,6 +4,7 @@ from rss_reader.logger.logger import Logger from rss_reader.interfaces.iloader.iloader import IHandler +from rss_reader.loader.loader import FromWebHandler from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) From 7ad4da470faeeeb56585a009e96e46a0b3482f55 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:38:02 +0300 Subject: [PATCH 327/973] import the BeautifulParser into the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index b5f2c178..8f4f5a35 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -5,6 +5,7 @@ from rss_reader.logger.logger import Logger from rss_reader.interfaces.iloader.iloader import IHandler from rss_reader.loader.loader import FromWebHandler +from rss_reader.parser.parser import BeautifulParser from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) From cacbaa76c58cef3b7d1f0b53b088b0e8ac81bebb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:42:38 +0300 Subject: [PATCH 328/973] create requirements.txt --- requirements.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..fb63633e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +autopep8==1.6.0 +certifi==2022.6.15 +charset-normalizer==2.0.12 +idna==3.3 +pycodestyle==2.8.0 +requests==2.28.0 +toml==0.10.2 +types-requests==2.27.31 +types-urllib3==1.26.15 +urllib3==1.26.9 From 70cb94d8435977b6df5149ee3ffe7eb88a363f8e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 20:45:05 +0300 Subject: [PATCH 329/973] install beautifulsoup4 --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index fb63633e..2bc1e1ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,11 @@ autopep8==1.6.0 +beautifulsoup4==4.11.1 certifi==2022.6.15 charset-normalizer==2.0.12 idna==3.3 pycodestyle==2.8.0 requests==2.28.0 +soupsieve==2.3.2.post1 toml==0.10.2 types-requests==2.27.31 types-urllib3==1.26.15 From e3f4f1dd8589fbb62b51e1f06958b5e3b18a6b44 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:20:33 +0300 Subject: [PATCH 330/973] return IHandler from the _get_data_from_resource --- rss_reader/starter/starter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 8f4f5a35..65081431 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -32,5 +32,7 @@ def run(self) -> None: log.info("Number was received.") - def _get_data_from_resource(self): - pass + def _get_data_from_resource(self) -> IHandler: + web_hendler = FromWebHandler(SuperCrawler, + BeautifulParser(BeautifulSoup)) + return web_hendler From 9ac7d871be541ea34575117e8723ad7deb915de3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:21:25 +0300 Subject: [PATCH 331/973] import the BeautifulSoup into the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 65081431..24446a46 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -1,6 +1,7 @@ from typing import Dict +from bs4 import BeautifulSoup from rss_reader.logger.logger import Logger from rss_reader.interfaces.iloader.iloader import IHandler From 45f1b639077e804bc1d914770322471c629a3b47 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:23:29 +0300 Subject: [PATCH 332/973] import the SuperCrawler into the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 24446a46..7156e782 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -7,6 +7,7 @@ from rss_reader.interfaces.iloader.iloader import IHandler from rss_reader.loader.loader import FromWebHandler from rss_reader.parser.parser import BeautifulParser +from rss_reader.crawler.crawler import SuperCrawler from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) From 909a57b4c969d144ca3f4599ee55ac34485bc4de Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:25:30 +0300 Subject: [PATCH 333/973] add a docstring to the _get_data_from_resource --- rss_reader/starter/starter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 7156e782..1584d32f 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -35,6 +35,11 @@ def run(self) -> None: log.info("Number was received.") def _get_data_from_resource(self) -> IHandler: + """Get data handler. + + :return: Data handler. + :rtype: IHandler + """ web_hendler = FromWebHandler(SuperCrawler, BeautifulParser(BeautifulSoup)) return web_hendler From 708b4577f54f2ef36c3dd13e620e12bb6a7e164c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:39:10 +0300 Subject: [PATCH 334/973] create iviewer module --- rss_reader/interfaces/iviewer/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iviewer/__init__.py diff --git a/rss_reader/interfaces/iviewer/__init__.py b/rss_reader/interfaces/iviewer/__init__.py new file mode 100644 index 00000000..e69de29b From d0e032cbdbace14bca5de900ac0c33e79fd4139f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:39:59 +0300 Subject: [PATCH 335/973] add a docstring to the iviewer module --- rss_reader/interfaces/iviewer/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iviewer/__init__.py b/rss_reader/interfaces/iviewer/__init__.py index e69de29b..32c8ba56 100644 --- a/rss_reader/interfaces/iviewer/__init__.py +++ b/rss_reader/interfaces/iviewer/__init__.py @@ -0,0 +1 @@ +"""This package contains the viewer interfaces.""" From ce5c4441ba2760e9c4e179a1e1262f9ed18ec9de Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:58:11 +0300 Subject: [PATCH 336/973] create the viewer module --- rss_reader/interfaces/iviewer/iviewer.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iviewer/iviewer.py diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py new file mode 100644 index 00000000..e69de29b From 8fc04ac8b4adfc4c3648011458b86ddb7c4d8ff3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:59:12 +0300 Subject: [PATCH 337/973] import th eannotations into the iviewer module --- rss_reader/interfaces/iviewer/iviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index e69de29b..9d48db4f 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -0,0 +1 @@ +from __future__ import annotations From f469f91c61845d26be00d27d26f848a7c9022445 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 21:59:35 +0300 Subject: [PATCH 338/973] import the ABC into the iviewer module --- rss_reader/interfaces/iviewer/iviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 9d48db4f..15a0cc05 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -1 +1,2 @@ from __future__ import annotations +from abc import ABC From c3e8aed0a16f376d63099dc50b83f44a00b3e9f2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:00:04 +0300 Subject: [PATCH 339/973] import the abstractmethod into the iviewer module --- rss_reader/interfaces/iviewer/iviewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 15a0cc05..62fef353 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -1,2 +1,2 @@ from __future__ import annotations -from abc import ABC +from abc import ABC, abstractmethod From 4f567c40abc28c8d23509519d197bc48e1b5fb4c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:00:49 +0300 Subject: [PATCH 340/973] create the IViewHandler class --- rss_reader/interfaces/iviewer/iviewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 62fef353..bea5a8cd 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -1,2 +1,6 @@ from __future__ import annotations from abc import ABC, abstractmethod + + +class IViewHandler(ABC): + pass From 8bfb4e0f288699d50988ae86589ccd5db5746a1d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:01:57 +0300 Subject: [PATCH 341/973] create the set_next method in the IViewHandler --- rss_reader/interfaces/iviewer/iviewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index bea5a8cd..0b9a9dd9 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -3,4 +3,5 @@ class IViewHandler(ABC): - pass + def set_next(self, handler: IViewHandler) -> IViewHandler: + pass From 812a0d564d88a0a9dbb6bb8063be1c4c6a3f4279 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:02:40 +0300 Subject: [PATCH 342/973] add the @abstractmethod to the set_next --- rss_reader/interfaces/iviewer/iviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 0b9a9dd9..b2218805 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -3,5 +3,6 @@ class IViewHandler(ABC): + @abstractmethod def set_next(self, handler: IViewHandler) -> IViewHandler: pass From 460594ce06f3c976a18885fe486b76f6b1c823d2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:03:35 +0300 Subject: [PATCH 343/973] add a dockstring to the set_next method --- rss_reader/interfaces/iviewer/iviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index b2218805..9d8707af 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -5,4 +5,5 @@ class IViewHandler(ABC): @abstractmethod def set_next(self, handler: IViewHandler) -> IViewHandler: + """Set the next viewer in the handler chain.""" pass From 7b5291fa6dcf4845353b096ec1fc99718962af24 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:04:48 +0300 Subject: [PATCH 344/973] create an abstract method show --- rss_reader/interfaces/iviewer/iviewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 9d8707af..ede4f3c4 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -7,3 +7,7 @@ class IViewHandler(ABC): def set_next(self, handler: IViewHandler) -> IViewHandler: """Set the next viewer in the handler chain.""" pass + + @abstractmethod + def show(self, data: dict): + pass From ffa4272e79de5283e30cc8d2645f41da40c6afd7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 22:05:34 +0300 Subject: [PATCH 345/973] add a docstring to the method show --- rss_reader/interfaces/iviewer/iviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index ede4f3c4..089033a6 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -10,4 +10,5 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: @abstractmethod def show(self, data: dict): + """Show data.""" pass From 9e5d5bcc0fff1ac3b714a544c07e4eb120169b01 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:25:50 +0300 Subject: [PATCH 346/973] add a dockstring to the iviewer module --- rss_reader/interfaces/iviewer/iviewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 089033a6..9b87f97d 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -1,3 +1,6 @@ +"""This module contains a set of interfaces for data viewers.""" + + from __future__ import annotations from abc import ABC, abstractmethod From f7d01aa070cb8b859d237c870f0eacf316f2b60f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:27:23 +0300 Subject: [PATCH 347/973] add a docstring to the IViewHandler --- rss_reader/interfaces/iviewer/iviewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 9b87f97d..42c60f06 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -6,6 +6,8 @@ class IViewHandler(ABC): + """Interface for a parser.""" + @abstractmethod def set_next(self, handler: IViewHandler) -> IViewHandler: """Set the next viewer in the handler chain.""" From 1b081996ec21a3076a0d4e5c58bb229081d3fce8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:29:28 +0300 Subject: [PATCH 348/973] create viewer package --- rss_reader/viewer/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/viewer/__init__.py diff --git a/rss_reader/viewer/__init__.py b/rss_reader/viewer/__init__.py new file mode 100644 index 00000000..e69de29b From 181db4879dc5d1f05c4077df19638f765f639503 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:30:37 +0300 Subject: [PATCH 349/973] add a docstring to the viewer package --- rss_reader/viewer/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/__init__.py b/rss_reader/viewer/__init__.py index e69de29b..172970ed 100644 --- a/rss_reader/viewer/__init__.py +++ b/rss_reader/viewer/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with viewer.""" From eb15b7baa36c9cc75341e0873f7768dffeb6f373 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:31:06 +0300 Subject: [PATCH 350/973] add a docstring to the parser package --- rss_reader/parser/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/__init__.py b/rss_reader/parser/__init__.py index e69de29b..29071829 100644 --- a/rss_reader/parser/__init__.py +++ b/rss_reader/parser/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with parser.""" From 6ec4ab9344a5347388605f8d148c3c12541541ee Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:31:42 +0300 Subject: [PATCH 351/973] create viewer module --- rss_reader/viewer/viewer.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/viewer/viewer.py diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py new file mode 100644 index 00000000..e69de29b From aa5fabf822222444d079133b28361782f68b15f3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:33:30 +0300 Subject: [PATCH 352/973] import the IViewHandler into the viewer module --- rss_reader/viewer/viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index e69de29b..37ced30e 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -0,0 +1,3 @@ + + +from rss_reader.interfaces.iviewer.iviewer import IViewHandler From 351413071435fb09a76e7d91340e6e698c620bc2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:34:05 +0300 Subject: [PATCH 353/973] create the AbstractViewHandler class --- rss_reader/viewer/viewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 37ced30e..ab561f74 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -1,3 +1,7 @@ from rss_reader.interfaces.iviewer.iviewer import IViewHandler + + +class AbstractViewHandler(IViewHandler): + pass From 93881d6509360524f034158521e493f9ee8cee57 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:34:53 +0300 Subject: [PATCH 354/973] create a _next_handler variable --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index ab561f74..63fe8574 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -4,4 +4,4 @@ class AbstractViewHandler(IViewHandler): - pass + _next_handler: Optional[IViewHandler] = None From c26b230cbbd4b77555274c6cefe4bd2d090e8c04 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:35:50 +0300 Subject: [PATCH 355/973] import the Optional into the viewer module --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 63fe8574..80cc0d98 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -1,5 +1,6 @@ +from typing import Optional from rss_reader.interfaces.iviewer.iviewer import IViewHandler From 29918f2c137f7154f1cb1660174088359749a33d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:36:40 +0300 Subject: [PATCH 356/973] create a set_next method --- rss_reader/viewer/viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 80cc0d98..9a09154b 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -6,3 +6,6 @@ class AbstractViewHandler(IViewHandler): _next_handler: Optional[IViewHandler] = None + + def set_next(self, handler: IViewHandler) -> IViewHandler: + pass From 57f2a6f345523fbf5ba910e0539b1d34a22455d6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:37:39 +0300 Subject: [PATCH 357/973] set the value of the _next_handler variable --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 9a09154b..7c478711 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -8,4 +8,4 @@ class AbstractViewHandler(IViewHandler): _next_handler: Optional[IViewHandler] = None def set_next(self, handler: IViewHandler) -> IViewHandler: - pass + self._next_handler = handler From 4c383f8d7b816b9f1f8d6488be9014ddf1ca4cdd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:38:26 +0300 Subject: [PATCH 358/973] return handler from the set_next --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 7c478711..12c52114 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -9,3 +9,4 @@ class AbstractViewHandler(IViewHandler): def set_next(self, handler: IViewHandler) -> IViewHandler: self._next_handler = handler + return handler From 1a76cecc017cfcff081d1da3a7a16d8aecbd4b39 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:39:20 +0300 Subject: [PATCH 359/973] add @send_log_of_start_function to the set_next --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 12c52114..3f6ef617 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -7,6 +7,7 @@ class AbstractViewHandler(IViewHandler): _next_handler: Optional[IViewHandler] = None + @send_log_of_start_function def set_next(self, handler: IViewHandler) -> IViewHandler: self._next_handler = handler return handler From 3a6ac7afe531004087a521528d34c1c4a5a6e25a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:40:10 +0300 Subject: [PATCH 360/973] import send_log_of_start_function to the viewer module --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 3f6ef617..3ed5d985 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -2,6 +2,7 @@ from typing import Optional from rss_reader.interfaces.iviewer.iviewer import IViewHandler +from rss_reader.decorator.decorator import send_log_of_start_function class AbstractViewHandler(IViewHandler): From b8222f79e1dd34cced82fcc47d199aa1125f7057 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:41:15 +0300 Subject: [PATCH 361/973] add a docstring to the set_next method --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 3ed5d985..aed21d01 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -10,5 +10,7 @@ class AbstractViewHandler(IViewHandler): @send_log_of_start_function def set_next(self, handler: IViewHandler) -> IViewHandler: + """Set the next viewer in the handler chain.""" + self._next_handler = handler return handler From edcac7d80b50f86bbad66bab4b519eaf226416c0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:43:31 +0300 Subject: [PATCH 362/973] create show method into the AbstractViewHandler --- rss_reader/viewer/viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index aed21d01..071b46e2 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -14,3 +14,6 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: self._next_handler = handler return handler + + def show(self, data: dict) -> None: + pass From 117bd56d69e3a476ef183ff51ce3be37360d0e36 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:44:33 +0300 Subject: [PATCH 363/973] add a return value to the show method --- rss_reader/interfaces/iviewer/iviewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 42c60f06..9a256996 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -14,6 +14,6 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: pass @abstractmethod - def show(self, data: dict): + def show(self, data: dict) -> None: """Show data.""" pass From fe7d7f6b0112197d780a0a0625e8969c6238ef25 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:46:24 +0300 Subject: [PATCH 364/973] run the execution of the show method down the chain --- rss_reader/viewer/viewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 071b46e2..62f942d8 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -16,4 +16,5 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: return handler def show(self, data: dict) -> None: - pass + if self._next_handler: + return self._next_handler.show(data) From 08b2c7d5516d41b53dadb1917c3cbf706c8dbce9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:47:18 +0300 Subject: [PATCH 365/973] add a docstring to the show method --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 62f942d8..eb1d98cb 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -16,5 +16,6 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: return handler def show(self, data: dict) -> None: + """Show data.""" if self._next_handler: return self._next_handler.show(data) From c558487b1cb719e3bde489c4011f26acfcfd6b0d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:48:03 +0300 Subject: [PATCH 366/973] add @send_log_of_start_function to the show method --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index eb1d98cb..41c8714e 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -15,6 +15,7 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: self._next_handler = handler return handler + @send_log_of_start_function def show(self, data: dict) -> None: """Show data.""" if self._next_handler: From ea980ac2d84955d12ca707ad70d0772285ec0433 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:49:40 +0300 Subject: [PATCH 367/973] add a docstring to the AbstractViewHandler class --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 41c8714e..4cabd3cb 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -6,6 +6,8 @@ class AbstractViewHandler(IViewHandler): + """The base class of the handler.""" + _next_handler: Optional[IViewHandler] = None @send_log_of_start_function From 3b3bb4222f9ef324742e2e3c277de5b286ed6196 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:50:32 +0300 Subject: [PATCH 368/973] create the StandartViewHandler class --- rss_reader/viewer/viewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 4cabd3cb..85931dde 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -22,3 +22,7 @@ def show(self, data: dict) -> None: """Show data.""" if self._next_handler: return self._next_handler.show(data) + + +class StandartViewHandler(AbstractViewHandler): + pass From 9b984f6d944232d8a91fcbc98ccbe5fe104ebd12 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:51:39 +0300 Subject: [PATCH 369/973] create a show method in the StandartViewHandler --- rss_reader/viewer/viewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 85931dde..ea51b272 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -25,4 +25,5 @@ def show(self, data: dict) -> None: class StandartViewHandler(AbstractViewHandler): - pass + def show(self, data: dict) -> None: + pass From 2dca81161a3771aa4e8291aa3c9f226f74031d8d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Fri, 24 Jun 2022 23:52:54 +0300 Subject: [PATCH 370/973] create the _get_info method --- rss_reader/viewer/viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index ea51b272..ff0fe6d6 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -27,3 +27,6 @@ def show(self, data: dict) -> None: class StandartViewHandler(AbstractViewHandler): def show(self, data: dict) -> None: pass + + def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: + pass From c81f620f0c86e521ab4dd2385400b8a3302c2780 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:17:44 +0300 Subject: [PATCH 371/973] get value from dictionary by key --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index ff0fe6d6..1aa966f7 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -29,4 +29,4 @@ def show(self, data: dict) -> None: pass def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: - pass + x = dict_.get(attr) From 5219b3803b207ec0c82ee6507362b3c6d1c6736d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:19:35 +0300 Subject: [PATCH 372/973] print the value if it exists --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 1aa966f7..3e28649a 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -30,3 +30,5 @@ def show(self, data: dict) -> None: def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: x = dict_.get(attr) + if x: + print(f'{str_}: {x}', end=end) From 351b31f9934aa223b216ab32768cd5404c2cb239 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:20:44 +0300 Subject: [PATCH 373/973] add a docstring to the _get_info --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 3e28649a..cf9be96f 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -29,6 +29,7 @@ def show(self, data: dict) -> None: pass def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: + """Print a string containing data from a dictionary.""" x = dict_.get(attr) if x: print(f'{str_}: {x}', end=end) From 3e46ee05193c4189045e27713bd6595670ae0b3f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:22:15 +0300 Subject: [PATCH 374/973] print the value of the title_web_resource key --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index cf9be96f..cbdaf4eb 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -26,7 +26,7 @@ def show(self, data: dict) -> None: class StandartViewHandler(AbstractViewHandler): def show(self, data: dict) -> None: - pass + self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From d6d493454599622d97792df9517d142f8f327b93 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:23:09 +0300 Subject: [PATCH 375/973] get all news for printing --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index cbdaf4eb..6aab8b1a 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -27,6 +27,7 @@ def show(self, data: dict) -> None: class StandartViewHandler(AbstractViewHandler): def show(self, data: dict) -> None: self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") + items = data.get('items') def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From 364b156b144322151713cc73a904f6f0bc3dfef0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:24:37 +0300 Subject: [PATCH 376/973] start iterating over the news list --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 6aab8b1a..5f8f2850 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -28,6 +28,8 @@ class StandartViewHandler(AbstractViewHandler): def show(self, data: dict) -> None: self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") items = data.get('items') + if isinstance(items, list): + for i in items: def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From 6facaed8eb4039bf102822370d57b42a3078caac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:26:00 +0300 Subject: [PATCH 377/973] print the values of the main news tags --- rss_reader/viewer/viewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 5f8f2850..6d18e140 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -30,6 +30,10 @@ def show(self, data: dict) -> None: items = data.get('items') if isinstance(items, list): for i in items: + self._get_info(i, "title", "Title") + self._get_info(i, "source", "Source") + self._get_info(i, "pubDate", "PubDate") + self._get_info(i, "link", "Link") def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From 1a2fa7108c2807f187eb577dc65b8a300ca35238 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:29:53 +0300 Subject: [PATCH 378/973] get a list for the content key --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 6d18e140..87a2da5e 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -34,6 +34,7 @@ def show(self, data: dict) -> None: self._get_info(i, "source", "Source") self._get_info(i, "pubDate", "PubDate") self._get_info(i, "link", "Link") + media_content = i.get("content") def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From 3c87cb39bcfb014df677fe8657056bf294f2d924 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:31:54 +0300 Subject: [PATCH 379/973] print the contents of the content key --- rss_reader/viewer/viewer.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 87a2da5e..430ace56 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -35,6 +35,13 @@ def show(self, data: dict) -> None: self._get_info(i, "pubDate", "PubDate") self._get_info(i, "link", "Link") media_content = i.get("content") + if media_content: + print("Media content:") + self._get_info(media_content, "title", + "[title of media content]") + self._get_info(media_content, "url", + "[source of media content]") + print('\n\n') def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From 9d31762ca34aa613bd1c8224a65f05e676059878 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:34:01 +0300 Subject: [PATCH 380/973] if items exists, then print it --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 430ace56..c90858c2 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -42,6 +42,8 @@ def show(self, data: dict) -> None: self._get_info(media_content, "url", "[source of media content]") print('\n\n') + elif items: + print(items) def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: """Print a string containing data from a dictionary.""" From 895dc3890b02ee68f243684332a0d4379bbecd92 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:36:35 +0300 Subject: [PATCH 381/973] add a dockstring to the show method in the StandartViewHandler --- rss_reader/viewer/viewer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index c90858c2..4a2f36cb 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -26,6 +26,12 @@ def show(self, data: dict) -> None: class StandartViewHandler(AbstractViewHandler): def show(self, data: dict) -> None: + """Show data. + + :param data: Dictionary with data to be printed on the screen. + :type data: dict + """ + self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") items = data.get('items') if isinstance(items, list): From 4f1ee9dfef2cf0525c0b4f5dc4b59abb32139fb3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:37:46 +0300 Subject: [PATCH 382/973] add a dockstring to the StandartViewHandler class --- rss_reader/viewer/viewer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 4a2f36cb..e0d5a485 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -25,6 +25,12 @@ def show(self, data: dict) -> None: class StandartViewHandler(AbstractViewHandler): + """Displays data on standard output + + It is the base handler. + Executed when others have failed to process the data. + """ + def show(self, data: dict) -> None: """Show data. From 56604dd93a0254efe111dfe870fbd13b743b34da Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:40:09 +0300 Subject: [PATCH 383/973] fix a docstring in the show method from IViewHandler --- rss_reader/interfaces/iviewer/iviewer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index 9a256996..af1e34fe 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -15,5 +15,9 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: @abstractmethod def show(self, data: dict) -> None: - """Show data.""" + """Show data. + + :param data: Dictionary with data to be printed on the screen. + :type data: dict + """ pass From bd2a8eb253b6fa88b6f7ebf8298c85e4462916c4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:41:34 +0300 Subject: [PATCH 384/973] fix a docstring in the set_next method from IViewHandler --- rss_reader/interfaces/iviewer/iviewer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index af1e34fe..b85913e6 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -10,7 +10,13 @@ class IViewHandler(ABC): @abstractmethod def set_next(self, handler: IViewHandler) -> IViewHandler: - """Set the next viewer in the handler chain.""" + """Set the next viewer in the handler chain. + + :param handler: Next handler. + :type handler: IViewHandler + :return: Handler. + :rtype: IViewHandler + """ pass @abstractmethod From 746f40ad35e69032716e9d047d89375de80efa4e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:42:19 +0300 Subject: [PATCH 385/973] fix a docstring in the set_next method from AbstractViewHandler --- rss_reader/viewer/viewer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index e0d5a485..05e7d052 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -12,7 +12,13 @@ class AbstractViewHandler(IViewHandler): @send_log_of_start_function def set_next(self, handler: IViewHandler) -> IViewHandler: - """Set the next viewer in the handler chain.""" + """Set the next viewer in the handler chain. + + :param handler: Next handler. + :type handler: IViewHandler + :return: Handler. + :rtype: IViewHandler + """ self._next_handler = handler return handler From 670a81d1d52dc48ad61634835a3d10d32b904cae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:42:48 +0300 Subject: [PATCH 386/973] fix a docstring in the show method from AbstractViewHandler --- rss_reader/viewer/viewer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 05e7d052..eb22956b 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -25,7 +25,11 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: @send_log_of_start_function def show(self, data: dict) -> None: - """Show data.""" + """Show data. + + :param data: Dictionary with data to be printed on the screen. + :type data: dict + """ if self._next_handler: return self._next_handler.show(data) From a677948b8b83d3f46d5ec0ee1487ec3411a3864e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:43:45 +0300 Subject: [PATCH 387/973] create the JSONViewHandler class --- rss_reader/viewer/viewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index eb22956b..85afa3f3 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -72,3 +72,7 @@ def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: x = dict_.get(attr) if x: print(f'{str_}: {x}', end=end) + + +class JSONViewHandler(AbstractViewHandler): + pass From 3d5075635f6e171929a71727b0e876b12f1e2af9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:44:43 +0300 Subject: [PATCH 388/973] override the __init__ method --- rss_reader/viewer/viewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 85afa3f3..87d7f8c7 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -75,4 +75,5 @@ def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: class JSONViewHandler(AbstractViewHandler): - pass + def __init__(self, request: Dict[str, str]) -> None: + self._request = request From 090b32a26c82eeed86e204be51b1628a0bad34b0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:45:21 +0300 Subject: [PATCH 389/973] import the Dict from the typing --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 87d7f8c7..e6d2707e 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -1,6 +1,6 @@ -from typing import Optional +from typing import Dict, Optional from rss_reader.interfaces.iviewer.iviewer import IViewHandler from rss_reader.decorator.decorator import send_log_of_start_function From 508ed1dcadd76e764bb4680aa728e9ff49689e08 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:47:39 +0300 Subject: [PATCH 390/973] add a dockstring to the __init__ in the JSONViewHandler --- rss_reader/viewer/viewer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index e6d2707e..b7f5cae0 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -76,4 +76,10 @@ def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: class JSONViewHandler(AbstractViewHandler): def __init__(self, request: Dict[str, str]) -> None: + """Initializer. + + :param request: A dictionary in which there may be a key + by which this handler will work. + :type request: Dict[str, str] + """ self._request = request From 4ae98bb94795618e818a8ba2689adb44eb129d59 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:55:36 +0300 Subject: [PATCH 391/973] create the show method in the JSONViewHandler --- rss_reader/viewer/viewer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index b7f5cae0..35cbe533 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -83,3 +83,6 @@ def __init__(self, request: Dict[str, str]) -> None: :type request: Dict[str, str] """ self._request = request + + def show(self, data: dict) -> None: + pass From e010a06dc9367b14271be485fcc8d9668376cabc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:57:05 +0300 Subject: [PATCH 392/973] output data in json format --- rss_reader/viewer/viewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 35cbe533..824f49ad 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -85,4 +85,5 @@ def __init__(self, request: Dict[str, str]) -> None: self._request = request def show(self, data: dict) -> None: - pass + if self._request.get('json'): + print(dumps(data, indent=3)) From 439fabb65b91d29e5b3ef204217f2423856bce35 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:57:56 +0300 Subject: [PATCH 393/973] pass the work to the next handler --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 824f49ad..c151f640 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -87,3 +87,5 @@ def __init__(self, request: Dict[str, str]) -> None: def show(self, data: dict) -> None: if self._request.get('json'): print(dumps(data, indent=3)) + else: + super().show(data) From 3479612562f2951d6ea3a6a89b0171da383447ed Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 00:59:42 +0300 Subject: [PATCH 394/973] import the dumps from json into the viewer module --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index c151f640..536c53fd 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -1,6 +1,8 @@ from typing import Dict, Optional +from json import dumps + from rss_reader.interfaces.iviewer.iviewer import IViewHandler from rss_reader.decorator.decorator import send_log_of_start_function From 216e2d0e186bcc87efcd1652b8761a5e4ac59489 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:01:03 +0300 Subject: [PATCH 395/973] add a docstring to the show method --- rss_reader/viewer/viewer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 536c53fd..4a37e294 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -87,6 +87,11 @@ def __init__(self, request: Dict[str, str]) -> None: self._request = request def show(self, data: dict) -> None: + """Display the data as a JSON structure. + + :param data: Dictionary with data to be printed on the screen. + :type data: dict + """ if self._request.get('json'): print(dumps(data, indent=3)) else: From f8e298d6ecb67fb4bf81bce0abe65002d6609be0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:03:03 +0300 Subject: [PATCH 396/973] add a docstrint to the JSONViewHandler --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 4a37e294..e0ce08d5 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -77,6 +77,8 @@ def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: class JSONViewHandler(AbstractViewHandler): + """Process data as JSON.""" + def __init__(self, request: Dict[str, str]) -> None: """Initializer. From e04040334a2a188dcb34294fa6cf17e31df42217 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:04:42 +0300 Subject: [PATCH 397/973] add a docstring to the viewer module --- rss_reader/viewer/viewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index e0ce08d5..cd4c505b 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -1,4 +1,8 @@ +"""This module contains specific viewers. +Viewers display data in the desired form. +Each viewer is called in a chain. +""" from typing import Dict, Optional from json import dumps From c4e814d48163608188e5a0e4b92d4591f5817e54 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:08:23 +0300 Subject: [PATCH 398/973] fix method show - add ensure_ascii=False --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index cd4c505b..5d93ef74 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -99,6 +99,6 @@ def show(self, data: dict) -> None: :type data: dict """ if self._request.get('json'): - print(dumps(data, indent=3)) + print(dumps(data, indent=3, ensure_ascii=False)) else: super().show(data) From 39305defbf11183a02d2f28e846a014812a41b98 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:09:44 +0300 Subject: [PATCH 399/973] create _get_viewer method in the Starte class --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 1584d32f..7c3ee5f7 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -43,3 +43,6 @@ def _get_data_from_resource(self) -> IHandler: web_hendler = FromWebHandler(SuperCrawler, BeautifulParser(BeautifulSoup)) return web_hendler + + def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: + pass From 9e17a585cff36439b82e768e5501c00dd6f428b7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:11:57 +0300 Subject: [PATCH 400/973] import IViewHandler into the starter module --- rss_reader/starter/starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 7c3ee5f7..4f8e46e5 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -8,6 +8,8 @@ from rss_reader.loader.loader import FromWebHandler from rss_reader.parser.parser import BeautifulParser from rss_reader.crawler.crawler import SuperCrawler +from rss_reader.interfaces.iviewer.iviewer import IViewHandler + from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) From c89d1eefea0a5b8984bcc28dffbeb2101b33f349 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:12:56 +0300 Subject: [PATCH 401/973] create a StandardViewHandler object --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 4f8e46e5..cf24d8fc 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -47,4 +47,4 @@ def _get_data_from_resource(self) -> IHandler: return web_hendler def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: - pass + stdout_ = StandartViewHandler() From 5649412367c7e4d37042140581fd04e99cb80cc6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:13:47 +0300 Subject: [PATCH 402/973] import StandartViewHandler into the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index cf24d8fc..c729592b 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -9,6 +9,7 @@ from rss_reader.parser.parser import BeautifulParser from rss_reader.crawler.crawler import SuperCrawler from rss_reader.interfaces.iviewer.iviewer import IViewHandler +from rss_reader.viewer.viewer import StandartViewHandler from .ecxeptions import NonNumericError From c83813be8bf82adc518812cd5a09d686c23955e0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:14:30 +0300 Subject: [PATCH 403/973] create a JSONViewHandler object --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index c729592b..e66af365 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -49,3 +49,4 @@ def _get_data_from_resource(self) -> IHandler: def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: stdout_ = StandartViewHandler() + json_ = JSONViewHandler(request) From 563c872d6ef3e3634cfc2deb3573805df296cfb2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:15:07 +0300 Subject: [PATCH 404/973] import JSONViewHandler into the starter module --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index e66af365..93e15419 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -9,7 +9,7 @@ from rss_reader.parser.parser import BeautifulParser from rss_reader.crawler.crawler import SuperCrawler from rss_reader.interfaces.iviewer.iviewer import IViewHandler -from rss_reader.viewer.viewer import StandartViewHandler +from rss_reader.viewer.viewer import StandartViewHandler, JSONViewHandler from .ecxeptions import NonNumericError From 5297855042b9e586737f0ae94f3340885d0acc18 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:15:56 +0300 Subject: [PATCH 405/973] create a chain of viewer handlers --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 93e15419..05081d6f 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -50,3 +50,4 @@ def _get_data_from_resource(self) -> IHandler: def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: stdout_ = StandartViewHandler() json_ = JSONViewHandler(request) + json_.set_next(stdout_) From ab62b936fd62352a4e8df14cb3c266619cb34693 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:16:40 +0300 Subject: [PATCH 406/973] return an IViewHandler object --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 05081d6f..a52eff3f 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -51,3 +51,4 @@ def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: stdout_ = StandartViewHandler() json_ = JSONViewHandler(request) json_.set_next(stdout_) + return json_ From 10ba405de745407609bfd4acb738993c4279f7fa Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 01:17:36 +0300 Subject: [PATCH 407/973] add a dockstring to the _get_viewer --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index a52eff3f..7bc74b99 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -48,6 +48,7 @@ def _get_data_from_resource(self) -> IHandler: return web_hendler def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: + """Get data viewer.""" stdout_ = StandartViewHandler() json_ = JSONViewHandler(request) json_.set_next(stdout_) From 059f325c37dd83a3e8846a0c05fd9abf69217116 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:03:56 +0300 Subject: [PATCH 408/973] get data handler --- rss_reader/starter/starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 7bc74b99..c8ef0dbf 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -37,6 +37,8 @@ def run(self) -> None: log.info("Number was received.") + data_handler = self._get_data_from_resource() + def _get_data_from_resource(self) -> IHandler: """Get data handler. From 199abada5060220f23999bb77b8f1fc73b728954 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:05:51 +0300 Subject: [PATCH 409/973] get data from internet --- rss_reader/starter/starter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index c8ef0dbf..5abd8915 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -38,6 +38,10 @@ def run(self) -> None: log.info("Number was received.") data_handler = self._get_data_from_resource() + data = data_handler.get_data('item', + 'channel > title', + self._argv.get('source'), + limit) def _get_data_from_resource(self) -> IHandler: """Get data handler. From 7be2d8b53b9cbf1844efdef51a150c6d9642e0ef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:07:36 +0300 Subject: [PATCH 410/973] catch the BadURLError in the run method --- rss_reader/starter/starter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 5abd8915..627bf0de 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -38,10 +38,14 @@ def run(self) -> None: log.info("Number was received.") data_handler = self._get_data_from_resource() - data = data_handler.get_data('item', - 'channel > title', - self._argv.get('source'), - limit) + try: + data = data_handler.get_data('item', + 'channel > title', + self._argv.get('source'), + limit) + except BadURLError as e: + log.exception(e) + raise def _get_data_from_resource(self) -> IHandler: """Get data handler. From 907e6716563a07ac46afe315871727f2c4182639 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:08:37 +0300 Subject: [PATCH 411/973] import BadURLError in the starter module --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 627bf0de..835c0a1a 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -8,6 +8,7 @@ from rss_reader.loader.loader import FromWebHandler from rss_reader.parser.parser import BeautifulParser from rss_reader.crawler.crawler import SuperCrawler +from rss_reader.crawler.exceptions import BadURLError from rss_reader.interfaces.iviewer.iviewer import IViewHandler from rss_reader.viewer.viewer import StandartViewHandler, JSONViewHandler From f0bf754ecc87f8e92957acf21fae3cc37a6deb24 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:13:24 +0300 Subject: [PATCH 412/973] if there is no data, then leave a message --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 835c0a1a..ffa6ec08 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -48,6 +48,9 @@ def run(self) -> None: log.exception(e) raise + if not data['items']: + data['items'] = 'Sorry, no news' + def _get_data_from_resource(self) -> IHandler: """Get data handler. From bb869ede9503f4e652047de846552dce5e0de6c9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:14:47 +0300 Subject: [PATCH 413/973] run the program when there is a source variable --- rss_reader/starter/starter.py | 48 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index ffa6ec08..537c3d4f 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -27,29 +27,31 @@ def __init__(self, argv: Dict[str, str]) -> None: self._argv = argv def run(self) -> None: - log.info("Get the number of requested news.") - - try: - lim = self._argv.get('limit') - limit = int(lim) if lim else None - except ValueError as e: - log.exception(e) - raise NonNumericError("--limit has a non-numeric value") from e - - log.info("Number was received.") - - data_handler = self._get_data_from_resource() - try: - data = data_handler.get_data('item', - 'channel > title', - self._argv.get('source'), - limit) - except BadURLError as e: - log.exception(e) - raise - - if not data['items']: - data['items'] = 'Sorry, no news' + + if self._argv['source']: + log.info("Get the number of requested news.") + + try: + lim = self._argv.get('limit') + limit = int(lim) if lim else None + except ValueError as e: + log.exception(e) + raise NonNumericError("--limit has a non-numeric value") from e + + log.info("Number was received.") + + data_handler = self._get_data_from_resource() + try: + data = data_handler.get_data('item', + 'channel > title', + self._argv.get('source'), + limit) + except BadURLError as e: + log.exception(e) + raise + + if not data['items']: + data['items'] = 'Sorry, no news' def _get_data_from_resource(self) -> IHandler: """Get data handler. From e0cce32791d20022f8f8d30842f1ec483747448d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:15:51 +0300 Subject: [PATCH 414/973] add a docstring to the run method --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 537c3d4f..32f695a1 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -27,6 +27,7 @@ def __init__(self, argv: Dict[str, str]) -> None: self._argv = argv def run(self) -> None: + """Program launch.""" if self._argv['source']: log.info("Get the number of requested news.") From dd076905d785018f4605591fc3abb2e099f17412 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:18:13 +0300 Subject: [PATCH 415/973] fix a docstring in _get_data_from_resource method --- rss_reader/starter/starter.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 32f695a1..d68dc7e2 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -55,11 +55,7 @@ def run(self) -> None: data['items'] = 'Sorry, no news' def _get_data_from_resource(self) -> IHandler: - """Get data handler. - - :return: Data handler. - :rtype: IHandler - """ + """Get data handler.""" web_hendler = FromWebHandler(SuperCrawler, BeautifulParser(BeautifulSoup)) return web_hendler From f7890b58535eb73728a372d494b0c802960e3869 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:19:51 +0300 Subject: [PATCH 416/973] add a docstring to the Starte class --- rss_reader/starter/starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index d68dc7e2..27302e3e 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -18,6 +18,8 @@ class Starter: + """A class to represent a starter main program.""" + def __init__(self, argv: Dict[str, str]) -> None: """Initializer. From c739c1177b9950cf152700b89edc965add3f16a4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:22:11 +0300 Subject: [PATCH 417/973] add a dockstring to the starter module --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 27302e3e..0c585844 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -1,4 +1,4 @@ - +"""The main configurator for launching the program.""" from typing import Dict from bs4 import BeautifulSoup From 2aa84946dc42d167b97740675d4c2931e038e7ad Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:23:30 +0300 Subject: [PATCH 418/973] import Starte to the __main__ --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 9c57700a..ae9bc8d2 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -3,6 +3,7 @@ from rss_reader.starter.base import (create_logger, init_arguments_functionality as iaf) +from rss_reader.starter.starter import Starter def main(): From e46c4d7affabbbf676994572355f229db297653e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:24:52 +0300 Subject: [PATCH 419/973] create a starter object --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index ae9bc8d2..67d855a2 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -9,6 +9,7 @@ def main(): args = iaf() create_logger(args.get('verbose')) + s = Starter() if __name__ == "__main__": From 537942af5ce0601670e7c449e2e661e361b75d85 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:25:40 +0300 Subject: [PATCH 420/973] start main program --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 67d855a2..539713ea 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -10,6 +10,7 @@ def main(): args = iaf() create_logger(args.get('verbose')) s = Starter() + s.run() if __name__ == "__main__": From 300a59d1ecc3a20519e2ad6a4eb2dfc24139ed30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:32:41 +0300 Subject: [PATCH 421/973] add the args argument to the run method --- rss_reader/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 539713ea..131fa69e 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -9,7 +9,7 @@ def main(): args = iaf() create_logger(args.get('verbose')) - s = Starter() + s = Starter(args) s.run() From 4cb1fe189c4d5fe0ff4ed60042468bb67d6998e0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:46:57 +0300 Subject: [PATCH 422/973] get viewer --- rss_reader/starter/starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 0c585844..62cc4ecd 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -56,6 +56,8 @@ def run(self) -> None: if not data['items']: data['items'] = 'Sorry, no news' + viewer = self._get_viewer(self._argv) + def _get_data_from_resource(self) -> IHandler: """Get data handler.""" web_hendler = FromWebHandler(SuperCrawler, From 078af23d223d5c421d2b04171449fe24a21d089a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:47:27 +0300 Subject: [PATCH 423/973] show data --- rss_reader/starter/starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 62cc4ecd..f2b1b689 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -57,6 +57,7 @@ def run(self) -> None: data['items'] = 'Sorry, no news' viewer = self._get_viewer(self._argv) + viewer.show(data) def _get_data_from_resource(self) -> IHandler: """Get data handler.""" From 693862ab0b91d4ef395896f8698beb7a50e5dc1c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:48:30 +0300 Subject: [PATCH 424/973] install lxml --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2bc1e1ac..adfd3129 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ beautifulsoup4==4.11.1 certifi==2022.6.15 charset-normalizer==2.0.12 idna==3.3 +lxml==4.9.0 pycodestyle==2.8.0 requests==2.28.0 soupsieve==2.3.2.post1 From 80b806f99eaf09912eda195e6e0f72c25c82b3a3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:53:06 +0300 Subject: [PATCH 425/973] change the type of the verbose argument --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 4109a5d3..76c29ddb 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -57,7 +57,7 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: return vars(namespace_) -def create_logger(verbose: Optional[str]) -> None: +def create_logger(verbose: bool) -> None: if verbose: config: ISetLoggerConfig = StreamHandlerConfig() else: From 46dfad2178fdeb0ebe8b193c199819a8e5a7e6c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 08:53:58 +0300 Subject: [PATCH 426/973] delet Optional from base module --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 76c29ddb..8a2fee24 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -5,7 +5,7 @@ import argparse -from typing import Dict, Optional +from typing import Dict from rss_reader.logger.logger import (Logger, StreamHandlerConfig, NullHandlerConfig) From cd8504ed866149d5c6c808eec2f9cd9137efd307 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 09:47:10 +0300 Subject: [PATCH 427/973] add the Logger to the __main__ --- rss_reader/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 131fa69e..6237e7c3 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -4,7 +4,7 @@ from rss_reader.starter.base import (create_logger, init_arguments_functionality as iaf) from rss_reader.starter.starter import Starter - +from rss_reader.logger.logger import Logger def main(): args = iaf() From 0d8d0f67cfc2eee0b85c91d365a30ac6faa58d27 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 09:49:44 +0300 Subject: [PATCH 428/973] get the program logger in the __main__ --- rss_reader/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 6237e7c3..988c495c 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -6,10 +6,13 @@ from rss_reader.starter.starter import Starter from rss_reader.logger.logger import Logger + def main(): args = iaf() create_logger(args.get('verbose')) s = Starter(args) + + log = Logger.get_logger(__name__) s.run() From 1e6da26e71a5cd36a39bdb87de3bea38422e015a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 09:51:16 +0300 Subject: [PATCH 429/973] move the Starter import to the main function --- rss_reader/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 988c495c..908d2c9d 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -3,13 +3,14 @@ from rss_reader.starter.base import (create_logger, init_arguments_functionality as iaf) -from rss_reader.starter.starter import Starter from rss_reader.logger.logger import Logger def main(): args = iaf() create_logger(args.get('verbose')) + + from rss_reader.starter.starter import Starter s = Starter(args) log = Logger.get_logger(__name__) From 1b0b822d1d7400420ab1ba4de25eb13e0401a1ed Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 10:34:43 +0300 Subject: [PATCH 430/973] fix a docstring of the __main__ module --- rss_reader/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 908d2c9d..7deaf18d 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -1,4 +1,4 @@ -"""Program entry point.""" +"""Entry point to the program.""" from rss_reader.starter.base import (create_logger, From 7cf5bc10a45c4733dbdb9554efc271fa40393d7c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 12:11:28 +0300 Subject: [PATCH 431/973] fix display of missing news --- rss_reader/viewer/viewer.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 5d93ef74..2223ccc3 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -54,9 +54,13 @@ def show(self, data: dict) -> None: :type data: dict """ - self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") + self._get_info(data, "title_web_resource", "\nFeed: ", + alternative='no data', end="\n\n\n") items = data.get('items') - if isinstance(items, list): + is_list = isinstance(items, list) + if is_list and len(items) == 1: + self._get_info(items[0], "no news", "News") + elif is_list: for i in items: self._get_info(i, "title", "Title") self._get_info(i, "source", "Source") @@ -70,14 +74,15 @@ def show(self, data: dict) -> None: self._get_info(media_content, "url", "[source of media content]") print('\n\n') - elif items: - print(items) - def _get_info(self, dict_: dict, attr: str, str_: str, end='\n') -> None: + def _get_info(self, dict_: dict, attr: str, str_: str, + alternative: str = '', end='\n') -> None: """Print a string containing data from a dictionary.""" x = dict_.get(attr) if x: print(f'{str_}: {x}', end=end) + elif alternative: + print(f'{str_}: {alternative}', end=end) class JSONViewHandler(AbstractViewHandler): From b3063785f9127c20a037f6d8b41c5e4d5474ff32 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 12:19:11 +0300 Subject: [PATCH 432/973] add a list with information for missing news --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index f2b1b689..4a977c73 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -54,7 +54,7 @@ def run(self) -> None: raise if not data['items']: - data['items'] = 'Sorry, no news' + data['items'] = [{'no news': 'Sorry, no news'}] viewer = self._get_viewer(self._argv) viewer.show(data) From 83bd06da4de45489e7dcd94acdd8b469552df76e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 12:41:26 +0300 Subject: [PATCH 433/973] fix check for no news --- rss_reader/viewer/viewer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 2223ccc3..a3a4a935 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -58,7 +58,12 @@ def show(self, data: dict) -> None: alternative='no data', end="\n\n\n") items = data.get('items') is_list = isinstance(items, list) - if is_list and len(items) == 1: + is_now_news = False + for i in items: + if "no news" in i: + is_now_news = True + + if is_list and is_now_news: self._get_info(items[0], "no news", "News") elif is_list: for i in items: From b2336a3b66122a229535436cc673beef34e57e88 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 12:44:40 +0300 Subject: [PATCH 434/973] fix the type checking order of the items element --- rss_reader/viewer/viewer.py | 38 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index a3a4a935..a9c94fd0 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -59,26 +59,28 @@ def show(self, data: dict) -> None: items = data.get('items') is_list = isinstance(items, list) is_now_news = False - for i in items: - if "no news" in i: - is_now_news = True - if is_list and is_now_news: - self._get_info(items[0], "no news", "News") - elif is_list: + if is_list: for i in items: - self._get_info(i, "title", "Title") - self._get_info(i, "source", "Source") - self._get_info(i, "pubDate", "PubDate") - self._get_info(i, "link", "Link") - media_content = i.get("content") - if media_content: - print("Media content:") - self._get_info(media_content, "title", - "[title of media content]") - self._get_info(media_content, "url", - "[source of media content]") - print('\n\n') + if "no news" in i: + is_now_news = True + + if is_now_news: + self._get_info(items[0], "no news", "News") + else: + for i in items: + self._get_info(i, "title", "Title") + self._get_info(i, "source", "Source") + self._get_info(i, "pubDate", "PubDate") + self._get_info(i, "link", "Link") + media_content = i.get("content") + if media_content: + print("Media content:") + self._get_info(media_content, "title", + "[title of media content]") + self._get_info(media_content, "url", + "[source of media content]") + print('\n\n') def _get_info(self, dict_: dict, attr: str, str_: str, alternative: str = '', end='\n') -> None: From c6417aff85319fec7b946bb5fb7934046c869341 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 12:52:11 +0300 Subject: [PATCH 435/973] catch the MissingSchema error in the crawler module --- rss_reader/crawler/crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 8aeb383c..379a67c4 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -2,6 +2,7 @@ from requests import Response, get, ConnectionError +from requests.exceptions import MissingSchema from rss_reader.interfaces.icrawler.icrawler import ICrawler from rss_reader.decorator.decorator import send_log_of_start_function @@ -45,6 +46,8 @@ def _get_response(self) -> Response: req = get(self._url) except ConnectionError as e: raise BadURLError(self._url) from e + except MissingSchema as e: + raise BadURLError(self._url) from e return req def _get_content(self, req: Response) -> bytes: From d95ee273af07e778a4cb75cfe70e04dffd5ee02d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 13:48:46 +0300 Subject: [PATCH 436/973] import the NonNumericError into the __main__ module --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 7deaf18d..a9785fda 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -4,6 +4,7 @@ from rss_reader.starter.base import (create_logger, init_arguments_functionality as iaf) from rss_reader.logger.logger import Logger +from rss_reader.starter.ecxeptions import NonNumericError def main(): From 76ceba7d81b0f5e607b73e071e6e7213a1d36bcb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 13:49:13 +0300 Subject: [PATCH 437/973] import the EmptyListError into the __main__ module --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index a9785fda..c336d69f 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -5,6 +5,7 @@ init_arguments_functionality as iaf) from rss_reader.logger.logger import Logger from rss_reader.starter.ecxeptions import NonNumericError +from rss_reader.parser.exceptions import EmptyListError def main(): From bd9fcc6f4f96631c2e59979d8455e3625346378a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 13:49:40 +0300 Subject: [PATCH 438/973] import the BadURLError into the __main__ module --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index c336d69f..b7d4d607 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -6,6 +6,7 @@ from rss_reader.logger.logger import Logger from rss_reader.starter.ecxeptions import NonNumericError from rss_reader.parser.exceptions import EmptyListError +from rss_reader.crawler.exceptions import BadURLError def main(): From 1aabf35c4a3ed459acbcc11a5b6215480af39566 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 13:50:52 +0300 Subject: [PATCH 439/973] catch the NonNumericError in the main function --- rss_reader/__main__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index b7d4d607..85cc8e13 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -17,7 +17,13 @@ def main(): s = Starter(args) log = Logger.get_logger(__name__) - s.run() + + try: + s.run() + except NonNumericError as e: + print(f'Sorry we have to stop working. Because:') + print(f'\t {e}') + log.error(e) if __name__ == "__main__": From 5c42c0acd1619e96c9801d3b33a120d615820032 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 13:51:26 +0300 Subject: [PATCH 440/973] catch the BadURLError in the main function --- rss_reader/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 85cc8e13..8a2cf0ac 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -24,6 +24,10 @@ def main(): print(f'Sorry we have to stop working. Because:') print(f'\t {e}') log.error(e) + except BadURLError as e: + print(f'Sorry we have to stop working. Because:') + print(f'\t {e}') + log.error(e) if __name__ == "__main__": From 41e7e84b34a8a063d6966bd9cf9527c247d38914 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:02:59 +0300 Subject: [PATCH 441/973] catch common errors in the __main__ module --- rss_reader/__main__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 8a2cf0ac..1e528965 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -28,6 +28,11 @@ def main(): print(f'Sorry we have to stop working. Because:') print(f'\t {e}') log.error(e) + except Exception as e: + s = ('Sorry, we have to stop working. Something went wrong.' + 'We are terribly sorry.') + print(s) + log.error(e) if __name__ == "__main__": From 7767cd6cddfd102fdef88c5ac650c0f72e5abdeb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:03:50 +0300 Subject: [PATCH 442/973] correct commas in the text --- rss_reader/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 1e528965..6fad9a40 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -21,11 +21,11 @@ def main(): try: s.run() except NonNumericError as e: - print(f'Sorry we have to stop working. Because:') + print(f'Sorry, we have to stop working. Because:') print(f'\t {e}') log.error(e) except BadURLError as e: - print(f'Sorry we have to stop working. Because:') + print(f'Sorry, we have to stop working. Because:') print(f'\t {e}') log.error(e) except Exception as e: From 311ee724af0ae99c0674d1932f9821922a712e84 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:07:10 +0300 Subject: [PATCH 443/973] del the EmptyListError from the __main__ module --- rss_reader/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 6fad9a40..36d9ec49 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -5,7 +5,6 @@ init_arguments_functionality as iaf) from rss_reader.logger.logger import Logger from rss_reader.starter.ecxeptions import NonNumericError -from rss_reader.parser.exceptions import EmptyListError from rss_reader.crawler.exceptions import BadURLError From 7d082c00f2510f449847dcf7d66c143d7377bb3b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:11:39 +0300 Subject: [PATCH 444/973] log the start of program execution --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 36d9ec49..d2a979c9 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -18,6 +18,7 @@ def main(): log = Logger.get_logger(__name__) try: + log.info("Start the program.") s.run() except NonNumericError as e: print(f'Sorry, we have to stop working. Because:') From 1345e4404db7242dbdd844a4f125b900fba3a8ad Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:13:18 +0300 Subject: [PATCH 445/973] add a finally block for errors --- rss_reader/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index d2a979c9..509b53d6 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -33,6 +33,8 @@ def main(): 'We are terribly sorry.') print(s) log.error(e) + finally: + exit() if __name__ == "__main__": From c1d1e7fccb0f674e0f0734eea2011721dac6b9a1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:15:27 +0300 Subject: [PATCH 446/973] log the stop of program execution --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 509b53d6..1f9b1b33 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -34,6 +34,7 @@ def main(): print(s) log.error(e) finally: + log.info("Stop the program.") exit() From a8eb6ca744c74494fe9e5ad9d2d3d273cc514fa4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:20:00 +0300 Subject: [PATCH 447/973] fix. getting program logger move up --- rss_reader/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 1f9b1b33..839b543a 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -9,14 +9,14 @@ def main(): + args = iaf() create_logger(args.get('verbose')) + log = Logger.get_logger(__name__) from rss_reader.starter.starter import Starter s = Starter(args) - log = Logger.get_logger(__name__) - try: log.info("Start the program.") s.run() From a152da890f605df1be1a6cf7534343f819ef254a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:23:30 +0300 Subject: [PATCH 448/973] log the creation of the Starter object --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 839b543a..112ee7d1 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -15,6 +15,7 @@ def main(): log = Logger.get_logger(__name__) from rss_reader.starter.starter import Starter + log.debug("Create a Starter object.") s = Starter(args) try: From 6cb30cf400fbc484e45bc94a97a830a12fa002fd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:28:29 +0300 Subject: [PATCH 449/973] log the beginning and end of the parser creation --- rss_reader/loader/loader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 55553c25..2d125be3 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -64,7 +64,10 @@ def get_data(self, cr = self._crawler(source) response_ = cr.get_data() + log.debug('Start creating the parser.') self._parser.create_parser(markup=response_) + log.debug('Stop creating the parser.') + title_text = next(self._parser.get_tags_text( selector=title_tag)) items = self._parser.get_items( From 57d66c1485e9388c9616a12f21087af8c0afd60b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:32:08 +0300 Subject: [PATCH 450/973] log the beginning and end of receiving the parsed data --- rss_reader/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 2d125be3..e19175bc 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -68,10 +68,12 @@ def get_data(self, self._parser.create_parser(markup=response_) log.debug('Stop creating the parser.') + log.debug('Start getting parsed data.') title_text = next(self._parser.get_tags_text( selector=title_tag)) items = self._parser.get_items( self.template, name=tag_name, limit_elms=limit) + log.debug('Stop getting parsed data.') log.info('Start generating results.') result = {'title_web_resource': title_text} From 62d7d771fa8966ef0db1314568e433c28122abb1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:33:54 +0300 Subject: [PATCH 451/973] change the logging level from info to debug --- rss_reader/loader/loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index e19175bc..16aae7a1 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -75,10 +75,10 @@ def get_data(self, self.template, name=tag_name, limit_elms=limit) log.debug('Stop getting parsed data.') - log.info('Start generating results.') + log.debug('Start generating results.') result = {'title_web_resource': title_text} items_dict = {'items': items} result.update(items_dict) - log.info('Result was formed.') + log.debug('Result was formed.') return result From 8db2205fa5edf0e68377a376f37c096ad4011fcf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:55:36 +0300 Subject: [PATCH 452/973] import the Logger to the crawler module --- rss_reader/crawler/crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 379a67c4..4ea990af 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -6,6 +6,7 @@ from rss_reader.interfaces.icrawler.icrawler import ICrawler from rss_reader.decorator.decorator import send_log_of_start_function +from rss_reader.logger.logger import Logger from .exceptions import BadURLError, FailStatusCodeError From a2a2a96f8cd5c77a6e3ef542bf92a42d7683e9d3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 14:57:05 +0300 Subject: [PATCH 453/973] get the logger of the program in the crawler module --- rss_reader/crawler/crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 4ea990af..fa11578e 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -9,6 +9,8 @@ from rss_reader.logger.logger import Logger from .exceptions import BadURLError, FailStatusCodeError +log = Logger.get_logger(__name__) + class SuperCrawler(ICrawler): """A class to represent a crawler.""" From 10b9ebb949f73531aa457cbd3c1a0bb0719f0ae4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:16:22 +0300 Subject: [PATCH 454/973] fix. make the return value a f-string --- rss_reader/crawler/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/exceptions.py b/rss_reader/crawler/exceptions.py index 085c4d6c..4b4e1aaa 100644 --- a/rss_reader/crawler/exceptions.py +++ b/rss_reader/crawler/exceptions.py @@ -20,4 +20,4 @@ def __init__(self, status_code, *args, **kwargs) -> None: super().__init__(*args, **kwargs) def __str__(self) -> str: - return 'An unsupported HTTP status code returned. ({self.status_code})' + return f'An unsupported HTTP status code returned. (code = {self.status_code})' From 677006748a78b8b1616259a0c986316215411ebe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:18:32 +0300 Subject: [PATCH 455/973] cath the FailStatusCodeError in the get_data --- rss_reader/crawler/crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index fa11578e..9faca03a 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -36,6 +36,8 @@ def get_data(self) -> bytes: status = self._get_status(r) if status == 200: return self._get_content(r) + log.error( + f'An unsupported HTTP status code returned. (code = {status})') raise FailStatusCodeError(status) def _get_response(self) -> Response: From 49eb7a2bf94ce670b4b566cf9fef1b440d5d9bfc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:22:25 +0300 Subject: [PATCH 456/973] log the ConnectionError in the _get_response --- rss_reader/crawler/crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 9faca03a..5159745d 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -50,6 +50,8 @@ def _get_response(self) -> Response: try: req = get(self._url) except ConnectionError as e: + s = f'It is not possible to get data for the given url ({self._url})' + log.error(s) raise BadURLError(self._url) from e except MissingSchema as e: raise BadURLError(self._url) from e From db306b376dc19e470926060a81904b0e9f3940f8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:23:24 +0300 Subject: [PATCH 457/973] log the MissingSchema error in the _get_response --- rss_reader/crawler/crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 5159745d..15007be1 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -54,6 +54,8 @@ def _get_response(self) -> Response: log.error(s) raise BadURLError(self._url) from e except MissingSchema as e: + s = f'It is not possible to get data for the given url ({self._url})' + log.error(s) raise BadURLError(self._url) from e return req From 1bb0c1330b0d90c78941b8f962857f7f4921aec0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:46:59 +0300 Subject: [PATCH 458/973] change the logging level from exception to error --- rss_reader/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/parser.py b/rss_reader/parser/parser.py index a1c7f618..9b6a28b3 100644 --- a/rss_reader/parser/parser.py +++ b/rss_reader/parser/parser.py @@ -64,7 +64,7 @@ def get_tags_text(self, tags = self._select(selector, limit_elms) if not tags: - log.exception("No matching tags. Maybe the selector is wrong.") + log.error("No matching tags. Maybe the selector is wrong.") raise EmptyListError( "No matching tags. Maybe the selector is wrong.") From a6cf0a91dff80c727f549182cde10ceab715396b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:48:57 +0300 Subject: [PATCH 459/973] change the logging level from exception to error --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 4a977c73..0279849e 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -50,7 +50,7 @@ def run(self) -> None: self._argv.get('source'), limit) except BadURLError as e: - log.exception(e) + log.error(e) raise if not data['items']: From 7d6f073a6fbb0368800d7e8c227ea88e889cfd67 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:52:49 +0300 Subject: [PATCH 460/973] change the logging level from exception to error in run method --- rss_reader/starter/starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 0279849e..e7ac4874 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -38,7 +38,7 @@ def run(self) -> None: lim = self._argv.get('limit') limit = int(lim) if lim else None except ValueError as e: - log.exception(e) + log.error(e) raise NonNumericError("--limit has a non-numeric value") from e log.info("Number was received.") From d890aa1611ffac3e29778a6eeded3188354b7c75 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:54:24 +0300 Subject: [PATCH 461/973] log the receipt of the viewer object in the run method --- rss_reader/starter/starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index e7ac4874..07b1428f 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -56,7 +56,10 @@ def run(self) -> None: if not data['items']: data['items'] = [{'no news': 'Sorry, no news'}] + log.info("Start getting the viewer object.") viewer = self._get_viewer(self._argv) + log.info("Stop getting the viewer object.") + viewer.show(data) def _get_data_from_resource(self) -> IHandler: From 43f3cf9a6712daf7966ab03d565b529acde6c9d5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:57:06 +0300 Subject: [PATCH 462/973] import the Logget to the viewer module --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index a9c94fd0..43f54c92 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -9,6 +9,7 @@ from rss_reader.interfaces.iviewer.iviewer import IViewHandler from rss_reader.decorator.decorator import send_log_of_start_function +from rss_reader.logger.logger import Logger class AbstractViewHandler(IViewHandler): From 2a076a0337d4c97a8d7e0641237562d924919b62 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 15:58:04 +0300 Subject: [PATCH 463/973] get the program logger in the viewer module --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 43f54c92..b3590a84 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -11,6 +11,8 @@ from rss_reader.decorator.decorator import send_log_of_start_function from rss_reader.logger.logger import Logger +log = Logger.get_logger(__name__) + class AbstractViewHandler(IViewHandler): """The base class of the handler.""" From dca13825138bc33080298bd71c877344a481145d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 16:01:05 +0300 Subject: [PATCH 464/973] log data display in the run method --- rss_reader/starter/starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 07b1428f..8d70ef2e 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -60,7 +60,9 @@ def run(self) -> None: viewer = self._get_viewer(self._argv) log.info("Stop getting the viewer object.") + log.info("Start displaying data.") viewer.show(data) + log.info("Stop displaying data.") def _get_data_from_resource(self) -> IHandler: """Get data handler.""" From 2e9b7b1c7dcadab89089cca5dd6049de7045f348 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 16:04:47 +0300 Subject: [PATCH 465/973] log printing title in the show method from the StandartViewHandler --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index b3590a84..990021d0 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -56,7 +56,7 @@ def show(self, data: dict) -> None: :param data: Dictionary with data to be printed on the screen. :type data: dict """ - + log.debug("Print title.") self._get_info(data, "title_web_resource", "\nFeed: ", alternative='no data', end="\n\n\n") items = data.get('items') From ff988ce525c3fcc87e21ca158c639d073fb309a3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 16:06:57 +0300 Subject: [PATCH 466/973] log receipt of news in the show method --- rss_reader/viewer/viewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 990021d0..8b3f7e5b 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -59,7 +59,11 @@ def show(self, data: dict) -> None: log.debug("Print title.") self._get_info(data, "title_web_resource", "\nFeed: ", alternative='no data', end="\n\n\n") + + log.debug("Start getting news.") items = data.get('items') + log.debug("Start getting news.") + is_list = isinstance(items, list) is_now_news = False From ba232ad767a624c53fcbeb2435ea6e6f939e4e02 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 16:10:01 +0300 Subject: [PATCH 467/973] log no news in the show method --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 8b3f7e5b..24f1ab50 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -73,6 +73,7 @@ def show(self, data: dict) -> None: is_now_news = True if is_now_news: + log.info("No news.") self._get_info(items[0], "no news", "News") else: for i in items: From 721c5f5ce5dca529a7b2ae774da1edc81c8921bd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 16:15:12 +0300 Subject: [PATCH 468/973] log news output in the shoe method --- rss_reader/viewer/viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 24f1ab50..3f1a8c32 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -77,6 +77,7 @@ def show(self, data: dict) -> None: self._get_info(items[0], "no news", "News") else: for i in items: + log.debug("Start posting news.") self._get_info(i, "title", "Title") self._get_info(i, "source", "Source") self._get_info(i, "pubDate", "PubDate") @@ -89,6 +90,7 @@ def show(self, data: dict) -> None: self._get_info(media_content, "url", "[source of media content]") print('\n\n') + log.debug("Stop posting news.") def _get_info(self, dict_: dict, attr: str, str_: str, alternative: str = '', end='\n') -> None: From a2819f6a903b084123d37637067f47a1381f0807 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 16:18:20 +0300 Subject: [PATCH 469/973] log outputting news in json format --- rss_reader/viewer/viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 3f1a8c32..aa2f8d5d 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -120,6 +120,7 @@ def show(self, data: dict) -> None: :param data: Dictionary with data to be printed on the screen. :type data: dict """ + log.debug("Start outputting news in json format.") if self._request.get('json'): print(dumps(data, indent=3, ensure_ascii=False)) else: From a70dae3f06acdec9fd390d263a0a44ac529d1f12 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:18:19 +0300 Subject: [PATCH 470/973] add a docstring to the main function --- rss_reader/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 112ee7d1..29863d53 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -9,6 +9,7 @@ def main(): + """Entry point to the program.""" args = iaf() create_logger(args.get('verbose')) From a275899500259916c1d73266d8fee71c7bbd5f86 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:19:42 +0300 Subject: [PATCH 471/973] add a docstring to the crawler package --- rss_reader/crawler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/__init__.py b/rss_reader/crawler/__init__.py index e69de29b..b6743a1a 100644 --- a/rss_reader/crawler/__init__.py +++ b/rss_reader/crawler/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with crawlers.""" From bc4b2c1f685ccf42aaa9b32340b66df8dddfb650 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:24:27 +0300 Subject: [PATCH 472/973] add a docstring to the create_logger function --- rss_reader/starter/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 8a2fee24..09f4f57d 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -58,6 +58,13 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: def create_logger(verbose: bool) -> None: + """Create a logger. + + :param verbose: Parameter responsible for selecting a specific handler. + The handler is responsible for how the information is + displayed. + :type verbose: bool + """ if verbose: config: ISetLoggerConfig = StreamHandlerConfig() else: From 73a5642c48dad14801c82c6db6d20e8428ec70a7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:34:21 +0300 Subject: [PATCH 473/973] install pytest --- requirements.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/requirements.txt b/requirements.txt index adfd3129..fd44478e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,23 @@ +atomicwrites==1.4.0 +attrs==21.4.0 autopep8==1.6.0 beautifulsoup4==4.11.1 certifi==2022.6.15 charset-normalizer==2.0.12 +colorama==0.4.5 idna==3.3 +iniconfig==1.1.1 lxml==4.9.0 +packaging==21.3 +pluggy==1.0.0 +py==1.11.0 pycodestyle==2.8.0 +pyparsing==3.0.9 +pytest==7.1.2 requests==2.28.0 soupsieve==2.3.2.post1 toml==0.10.2 +tomli==2.0.1 types-requests==2.27.31 types-urllib3==1.26.15 urllib3==1.26.9 From 6e66cfd7fdb08a83612aeb5426144351d59324f2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:42:27 +0300 Subject: [PATCH 474/973] add the .coverage file to the .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4f1a202c..67c7af5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode .idea +.coverage __pycache__ \ No newline at end of file From bb581c19eb3fad9409f41798b5a04ff25985ef1e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:43:41 +0300 Subject: [PATCH 475/973] install pytest-cov --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index fd44478e..a22ffd7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ beautifulsoup4==4.11.1 certifi==2022.6.15 charset-normalizer==2.0.12 colorama==0.4.5 +coverage==6.4.1 idna==3.3 iniconfig==1.1.1 lxml==4.9.0 @@ -14,6 +15,7 @@ py==1.11.0 pycodestyle==2.8.0 pyparsing==3.0.9 pytest==7.1.2 +pytest-cov==3.0.0 requests==2.28.0 soupsieve==2.3.2.post1 toml==0.10.2 From 792072309ebbdde4e6c239e75ebb626619114957 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:56:12 +0300 Subject: [PATCH 476/973] create "tests" module for the crawler module --- rss_reader/crawler/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/crawler/tests/__init__.py diff --git a/rss_reader/crawler/tests/__init__.py b/rss_reader/crawler/tests/__init__.py new file mode 100644 index 00000000..e69de29b From b640712c83d9eeba3046d234ca70699f84cb99af Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 18:59:20 +0300 Subject: [PATCH 477/973] create tests for the SuperCrawler class --- rss_reader/crawler/tests/test_super_crawler.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/crawler/tests/test_super_crawler.py diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py new file mode 100644 index 00000000..e69de29b From 397aa05a6eb4b644cbbd422f58f93766edf3f152 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:00:28 +0300 Subject: [PATCH 478/973] create test_get_data function --- rss_reader/crawler/tests/test_super_crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index e69de29b..cc544888 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -0,0 +1,2 @@ +def test_get_data(monkeypatch): + pass From 76849abd8508c706a77ebe8151f033849d296d11 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:02:07 +0300 Subject: [PATCH 479/973] import pytest to the test_super_crawler module --- rss_reader/crawler/tests/test_super_crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index cc544888..2a85ff3e 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -1,2 +1,5 @@ +import pytest + + def test_get_data(monkeypatch): pass From 0743af2e995eda91d0ca1684112faadf424f5ad6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:44:51 +0300 Subject: [PATCH 480/973] import requests into the test_super_crawler module --- rss_reader/crawler/tests/test_super_crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 2a85ff3e..0b5966d7 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -1,4 +1,5 @@ import pytest +import requests def test_get_data(monkeypatch): From 8e53cb9a9c6993965624ada9443c539946a5cd10 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:46:09 +0300 Subject: [PATCH 481/973] create MockResponse class --- rss_reader/crawler/tests/test_super_crawler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 0b5966d7..a53a83c1 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -2,5 +2,9 @@ import requests +class MockResponse: + pass + + def test_get_data(monkeypatch): pass From 1d5b96adae9ccf7a44e02850679a4e43b4b1f979 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:47:24 +0300 Subject: [PATCH 482/973] override __init__ method in the MockResponse class --- rss_reader/crawler/tests/test_super_crawler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index a53a83c1..5d830b6f 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -3,7 +3,9 @@ class MockResponse: - pass + def __init__(self, content, status_code=200) -> None: + self.content = content + self.status_code = status_code def test_get_data(monkeypatch): From 8dec974df06225bb2129c372dd152289da5054a8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:48:09 +0300 Subject: [PATCH 483/973] create internal mock_get_data function --- rss_reader/crawler/tests/test_super_crawler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 5d830b6f..08ac5be7 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -9,4 +9,5 @@ def __init__(self, content, status_code=200) -> None: def test_get_data(monkeypatch): - pass + def mock_get_data(*args, **kwargs): + return MockResponse(b'') From d418a01678da97ca48fffcadfb1dc03b3857724f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:49:09 +0300 Subject: [PATCH 484/973] import SuperCrawler into the test_super_crawler module --- rss_reader/crawler/tests/test_super_crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 08ac5be7..08142787 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -1,6 +1,7 @@ import pytest import requests +from ..crawler import SuperCrawler class MockResponse: def __init__(self, content, status_code=200) -> None: From 735c2fa4f1313f7fd3f0395ba3927f80a912b2d8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:50:15 +0300 Subject: [PATCH 485/973] set the new behavior of the _get_response function --- rss_reader/crawler/tests/test_super_crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 08142787..7a2fe77c 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -3,6 +3,7 @@ from ..crawler import SuperCrawler + class MockResponse: def __init__(self, content, status_code=200) -> None: self.content = content @@ -12,3 +13,5 @@ def __init__(self, content, status_code=200) -> None: def test_get_data(monkeypatch): def mock_get_data(*args, **kwargs): return MockResponse(b'') + + monkeypatch.setattr(SuperCrawler, '_get_response', mock_get_data) From 9d419cf988d68a8b6681678725e24405af765139 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:50:45 +0300 Subject: [PATCH 486/973] create a new SuperCrawler class object --- rss_reader/crawler/tests/test_super_crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 7a2fe77c..103e2e30 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -15,3 +15,4 @@ def mock_get_data(*args, **kwargs): return MockResponse(b'') monkeypatch.setattr(SuperCrawler, '_get_response', mock_get_data) + data = SuperCrawler('https://news.yahoo888.com/rss/').get_data() From 449abd7eec7df5db795f45e61afa88dfec5c9d7c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:52:39 +0300 Subject: [PATCH 487/973] compare the received response of the get_data function --- rss_reader/crawler/tests/test_super_crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 103e2e30..53d1ef90 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -16,3 +16,5 @@ def mock_get_data(*args, **kwargs): monkeypatch.setattr(SuperCrawler, '_get_response', mock_get_data) data = SuperCrawler('https://news.yahoo888.com/rss/').get_data() + + assert isinstance(data, bytes) From 2115b40b1e0c89422630c694510471e97915b2ae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:54:18 +0300 Subject: [PATCH 488/973] add a dockstring to the MockResponse class --- rss_reader/crawler/tests/test_super_crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 53d1ef90..5e2b5665 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -5,6 +5,7 @@ class MockResponse: + """Mimics the behavior of the Response object.""" def __init__(self, content, status_code=200) -> None: self.content = content self.status_code = status_code From b4d0589b0e615095ba4c3531b358b696f36ffe4f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:56:32 +0300 Subject: [PATCH 489/973] add a docstring to the __init__ in the MockResponse class --- rss_reader/crawler/tests/test_super_crawler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 5e2b5665..38216979 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -7,6 +7,13 @@ class MockResponse: """Mimics the behavior of the Response object.""" def __init__(self, content, status_code=200) -> None: + """Initializer. + + :param content: The content to be returned. + :type content: str + :param status_code: HTTP status code, defaults to 200 + :type status_code: int, optional + """ self.content = content self.status_code = status_code From 4b6a4f2639529ec0c4360737de5bc7a21c91b48d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:57:54 +0300 Subject: [PATCH 490/973] add argument types to the __init__ method --- rss_reader/crawler/tests/test_super_crawler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 38216979..f17145ed 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -6,7 +6,8 @@ class MockResponse: """Mimics the behavior of the Response object.""" - def __init__(self, content, status_code=200) -> None: + + def __init__(self, content: str, status_code: int = 200) -> None: """Initializer. :param content: The content to be returned. From 43053bdb8f457ebc8ff08dd6833594985853e590 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 19:58:58 +0300 Subject: [PATCH 491/973] add a docstring to the test_get_data function --- rss_reader/crawler/tests/test_super_crawler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index f17145ed..5e2fe986 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -20,6 +20,11 @@ def __init__(self, content: str, status_code: int = 200) -> None: def test_get_data(monkeypatch): + """Checks the type of the returned object. + + Type must be a byte string. + """ + def mock_get_data(*args, **kwargs): return MockResponse(b'') From 7d0605954bb4e6ab86664ea1b759d459ecd6143a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:10:20 +0300 Subject: [PATCH 492/973] create the test_get_fail_error method --- rss_reader/crawler/tests/test_super_crawler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 5e2fe986..d31b37ca 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -32,3 +32,7 @@ def mock_get_data(*args, **kwargs): data = SuperCrawler('https://news.yahoo888.com/rss/').get_data() assert isinstance(data, bytes) + + +def test_get_fail_error(monkeypatch): + pass From 1f6898539a002e0cab35b43f55899b3d540ede21 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:13:44 +0300 Subject: [PATCH 493/973] create internal mock_get_status function --- rss_reader/crawler/tests/test_super_crawler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index d31b37ca..fad7e912 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -35,4 +35,6 @@ def mock_get_data(*args, **kwargs): def test_get_fail_error(monkeypatch): - pass + + def mock_get_status(*args, **kwargs): + return MockResponse(b'', 100) From 5f9f9b302db43926775602f4e83dbaab280a4a1f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:14:32 +0300 Subject: [PATCH 494/973] set the new behavior of the _get_status function --- rss_reader/crawler/tests/test_super_crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index fad7e912..6b0d5ea0 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -38,3 +38,5 @@ def test_get_fail_error(monkeypatch): def mock_get_status(*args, **kwargs): return MockResponse(b'', 100) + + monkeypatch.setattr(SuperCrawler, '_get_status', mock_get_status) From 543deed42a02159d7fc9c180425a7f0bc5f19c26 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:15:40 +0300 Subject: [PATCH 495/973] acknowledge the occurrence of the FailStatusCodeError error --- rss_reader/crawler/tests/test_super_crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 6b0d5ea0..fa2d61e8 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -40,3 +40,6 @@ def mock_get_status(*args, **kwargs): return MockResponse(b'', 100) monkeypatch.setattr(SuperCrawler, '_get_status', mock_get_status) + + with pytest.raises(FailStatusCodeError): + SuperCrawler('https://news.yahoo.com/rss/').get_data() From 160f6dc924a781baa38b1ef2b161a75c5d06064c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:16:53 +0300 Subject: [PATCH 496/973] import the FailStatusCodeError into the test_super_crawler --- rss_reader/crawler/tests/test_super_crawler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index fa2d61e8..e9bd9e95 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -2,6 +2,7 @@ import requests from ..crawler import SuperCrawler +from ..exceptions import FailStatusCodeError class MockResponse: From 59d65a068634d1cc6fcec9add9e25cf312471916 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:17:36 +0300 Subject: [PATCH 497/973] add a docstring to the test_get_fail_error --- rss_reader/crawler/tests/test_super_crawler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index e9bd9e95..7a5b4fc9 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -36,6 +36,10 @@ def mock_get_data(*args, **kwargs): def test_get_fail_error(monkeypatch): + """Check that a FailStatusCodeError exception is returned. + + An exception is thrown when status code is not equal to 200. + """ def mock_get_status(*args, **kwargs): return MockResponse(b'', 100) From 9f6471ad31be45ae455ecb63524ad6128ae06df3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:19:14 +0300 Subject: [PATCH 498/973] create test_fail_url_response function --- rss_reader/crawler/tests/test_super_crawler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 7a5b4fc9..d00aa65d 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -48,3 +48,7 @@ def mock_get_status(*args, **kwargs): with pytest.raises(FailStatusCodeError): SuperCrawler('https://news.yahoo.com/rss/').get_data() + + +def test_fail_url_response(monkeypatch): + pass From 40bc43ebc20717ea1bb511aec684837a1a3f30bc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:27:08 +0300 Subject: [PATCH 499/973] import the BadURLError into the test_super_crawler --- rss_reader/crawler/tests/test_super_crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index d00aa65d..7e723979 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -2,7 +2,7 @@ import requests from ..crawler import SuperCrawler -from ..exceptions import FailStatusCodeError +from ..exceptions import FailStatusCodeError, BadURLError class MockResponse: From 7b41af18db94a77fdcf46febf5c2790228c7931e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:28:35 +0300 Subject: [PATCH 500/973] create internal mock_get_error function --- rss_reader/crawler/tests/test_super_crawler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 7e723979..783bd871 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -51,4 +51,5 @@ def mock_get_status(*args, **kwargs): def test_fail_url_response(monkeypatch): - pass + def mock_get_error(*args, **kwargs): + raise ConnectionError From afedd044ddf4a357310f4329e4b6b7b012372cf6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:29:27 +0300 Subject: [PATCH 501/973] set the new behavior of the request.get function --- rss_reader/crawler/tests/test_super_crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 783bd871..9564fbed 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -53,3 +53,5 @@ def mock_get_status(*args, **kwargs): def test_fail_url_response(monkeypatch): def mock_get_error(*args, **kwargs): raise ConnectionError + + monkeypatch.setattr(requests, 'get', mock_get_error) From d9f3402ddc35d1d86be349536fc7cfc7f2df79e3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:30:36 +0300 Subject: [PATCH 502/973] acknowledge the occurrence of the BadURLError error --- rss_reader/crawler/tests/test_super_crawler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 9564fbed..dd4a373a 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -55,3 +55,6 @@ def mock_get_error(*args, **kwargs): raise ConnectionError monkeypatch.setattr(requests, 'get', mock_get_error) + + with pytest.raises(BadURLError): + SuperCrawler('https://news.yahoo888.com/rss/').get_data() From 8fec1505aa8860273e2042c63fd47ba80ce8814f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:31:27 +0300 Subject: [PATCH 503/973] add a dockstring to the test_fail_url_response --- rss_reader/crawler/tests/test_super_crawler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index dd4a373a..be5a9e33 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -51,6 +51,11 @@ def mock_get_status(*args, **kwargs): def test_fail_url_response(monkeypatch): + """Check that a BadURLError exception is returned. + + An exception is thrown when it is not possible to get data from the site. + """ + def mock_get_error(*args, **kwargs): raise ConnectionError From efb2e16b7c08ba6c1fa10daf1c74f8f4ffe26e80 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:33:35 +0300 Subject: [PATCH 504/973] add a docstring to the test_super_crawler module --- rss_reader/crawler/tests/test_super_crawler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index be5a9e33..68a5ec3d 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -1,3 +1,5 @@ +"""A test suite for the SuperCrawler class.""" + import pytest import requests From f88db48037f60cc4f9348c4ff9690fb9cc493d38 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:39:26 +0300 Subject: [PATCH 505/973] add a dockstring to the tests module of the crawler --- rss_reader/crawler/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/crawler/tests/__init__.py b/rss_reader/crawler/tests/__init__.py index e69de29b..624cba14 100644 --- a/rss_reader/crawler/tests/__init__.py +++ b/rss_reader/crawler/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for the crawler module.""" From 6145cb6318c4f1f8f524508b1ccc0117e08c79fe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:40:55 +0300 Subject: [PATCH 506/973] create "test" package in the logger package --- rss_reader/logger/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/logger/tests/__init__.py diff --git a/rss_reader/logger/tests/__init__.py b/rss_reader/logger/tests/__init__.py new file mode 100644 index 00000000..e69de29b From afb3c35cb6833bd07b9e80e5bda55b244e2fe36f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:42:07 +0300 Subject: [PATCH 507/973] add a dockstring to the tests package of the logger --- rss_reader/logger/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/__init__.py b/rss_reader/logger/tests/__init__.py index e69de29b..63621acf 100644 --- a/rss_reader/logger/tests/__init__.py +++ b/rss_reader/logger/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for the logger package.""" From 1c165a628c1852c1e9997178be3c2d7f7e951ac7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:45:18 +0300 Subject: [PATCH 508/973] create a test_logger module --- rss_reader/logger/tests/test_logger.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/logger/tests/test_logger.py diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py new file mode 100644 index 00000000..e69de29b From 0acb2512c94c8cb949dfcc3db9172855a9ba6270 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:54:44 +0300 Subject: [PATCH 509/973] create the test_stream_config function --- rss_reader/logger/tests/test_logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index e69de29b..d6cf06e6 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -0,0 +1,4 @@ + + +def test_stream_config(): + pass From 83fc363f95c452496790f35a9dd6c43a39e6b0ae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:56:47 +0300 Subject: [PATCH 510/973] create a new StreamHandlerConfig class object --- rss_reader/logger/tests/test_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index d6cf06e6..0c56c937 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -1,4 +1,4 @@ def test_stream_config(): - pass + sc = StreamHandlerConfig().set_config('name') From dccef29b2f3db40e5aecb6ae717047bcd2823d0c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:58:28 +0300 Subject: [PATCH 511/973] get logger from StreamHandlerConfig class --- rss_reader/logger/tests/test_logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 0c56c937..9a4b5d34 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -1,4 +1,5 @@ def test_stream_config(): - sc = StreamHandlerConfig().set_config('name') + sc = StreamHandlerConfig() + logger = sc.set_config('name') From 497fc8bf096ed4c9c88ec5d7c6f140122d572b75 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 22:59:43 +0300 Subject: [PATCH 512/973] compare the type of the received logger with the required one --- rss_reader/logger/tests/test_logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 9a4b5d34..bb4b12dc 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -3,3 +3,4 @@ def test_stream_config(): sc = StreamHandlerConfig() logger = sc.set_config('name') + assert isinstance(sc, Logger) From ae14db141349f87da2d0ac26253b488345fae09f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:00:25 +0300 Subject: [PATCH 513/973] import Logger from the logging package --- rss_reader/logger/tests/test_logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index bb4b12dc..98083011 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -1,3 +1,4 @@ +from logging import Logger def test_stream_config(): From 9c5edb7437e95ed4d69dfff4b2e313d5cf905871 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:01:18 +0300 Subject: [PATCH 514/973] import StreamHandlerConfig --- rss_reader/logger/tests/test_logger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 98083011..36a04994 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -1,5 +1,7 @@ from logging import Logger +from ..logger import StreamHandlerConfig + def test_stream_config(): sc = StreamHandlerConfig() From 84c664e62cb3a7fe4f901280d336bd8ae292c3a7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:02:58 +0300 Subject: [PATCH 515/973] fix from sc to logger --- rss_reader/logger/tests/test_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 36a04994..68262e3a 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -6,4 +6,4 @@ def test_stream_config(): sc = StreamHandlerConfig() logger = sc.set_config('name') - assert isinstance(sc, Logger) + assert isinstance(logger, Logger) From 8d9e1280b120d90643fa47ec2d3a75c1be1741ba Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:04:53 +0300 Subject: [PATCH 516/973] add a dockstring to the test_stream_config --- rss_reader/logger/tests/test_logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 68262e3a..56985a99 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -4,6 +4,7 @@ def test_stream_config(): + """Check that the type of logger returned is of type Logger.""" sc = StreamHandlerConfig() logger = sc.set_config('name') assert isinstance(logger, Logger) From ca70ea2cc58355456d6c182075b45d748b6b6ddc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:05:27 +0300 Subject: [PATCH 517/973] create test_null_config function --- rss_reader/logger/tests/test_logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 56985a99..dc8cb536 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -8,3 +8,7 @@ def test_stream_config(): sc = StreamHandlerConfig() logger = sc.set_config('name') assert isinstance(logger, Logger) + + +def test_null_config(): + pass From 67f1efc28ebc6106e74e55562d0a921f47d6b91f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:06:05 +0300 Subject: [PATCH 518/973] get logger from NullHandlerConfig class --- rss_reader/logger/tests/test_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index dc8cb536..51998a15 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -11,4 +11,4 @@ def test_stream_config(): def test_null_config(): - pass + sc = NullHandlerConfig() From 8d8ab525ee6abc0c0a43101fe2259fd83221f62d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:06:52 +0300 Subject: [PATCH 519/973] get logger from NullHandlerConfig class --- rss_reader/logger/tests/test_logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 51998a15..790c9b99 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -12,3 +12,4 @@ def test_stream_config(): def test_null_config(): sc = NullHandlerConfig() + logger = sc.set_config('name') From 3cf7453e008b0f3ee56ad01247c9f2020745b92d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:07:29 +0300 Subject: [PATCH 520/973] compare the type of the received logger with the required one --- rss_reader/logger/tests/test_logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 790c9b99..68cdf081 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -13,3 +13,4 @@ def test_stream_config(): def test_null_config(): sc = NullHandlerConfig() logger = sc.set_config('name') + assert isinstance(sc, Logger) From 5fb183b8550cdadfc98cc3a2b4d504263f9463e5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:08:07 +0300 Subject: [PATCH 521/973] import NullHandlerConfig --- rss_reader/logger/tests/test_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 68cdf081..93ce6323 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -1,6 +1,6 @@ from logging import Logger -from ..logger import StreamHandlerConfig +from ..logger import StreamHandlerConfig, NullHandlerConfig def test_stream_config(): From 860ac0f5b14f5d18ee4efd4b08770bf985adc71c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:08:34 +0300 Subject: [PATCH 522/973] fix from sc to logger --- rss_reader/logger/tests/test_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 93ce6323..f8c93bb2 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -13,4 +13,4 @@ def test_stream_config(): def test_null_config(): sc = NullHandlerConfig() logger = sc.set_config('name') - assert isinstance(sc, Logger) + assert isinstance(logger, Logger) From 424f56d76b279407fe1ae5ff17c49d236fa24291 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:10:04 +0300 Subject: [PATCH 523/973] add a docstring to the test_null_config --- rss_reader/logger/tests/test_logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index f8c93bb2..4d928f94 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -11,6 +11,7 @@ def test_stream_config(): def test_null_config(): + """Check that NullHandlerConfig returns the desired logger type.""" sc = NullHandlerConfig() logger = sc.set_config('name') assert isinstance(logger, Logger) From c23afe659dbe0a7f8acb5779b56a790d1b1ddb6c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:11:30 +0300 Subject: [PATCH 524/973] create the TestLogger class --- rss_reader/logger/tests/test_logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 4d928f94..c79d1731 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -15,3 +15,7 @@ def test_null_config(): sc = NullHandlerConfig() logger = sc.set_config('name') assert isinstance(logger, Logger) + + +class TestLogger(): + pass From c31ea7ae6bfac1646684ab19bb43aef08873a087 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:11:58 +0300 Subject: [PATCH 525/973] create test_setup_logger --- rss_reader/logger/tests/test_logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index c79d1731..6284f0dc 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -18,4 +18,5 @@ def test_null_config(): class TestLogger(): - pass + def test_setup_logger(self, logger_obj): + pass From de8738e74db6fd4ef94e27684f6f8d7a70a5c00b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:13:17 +0300 Subject: [PATCH 526/973] check that the setup_logger function returns the required logger type --- rss_reader/logger/tests/test_logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 6284f0dc..0d9c50bf 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -19,4 +19,5 @@ def test_null_config(): class TestLogger(): def test_setup_logger(self, logger_obj): - pass + o = logger_obj.setup_logger() + assert isinstance(o, Logger) From 9006f66cb1c13eda7082dbc4660ffb3ad5ec6108 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:19:00 +0300 Subject: [PATCH 527/973] add a docstring to the test_setup_logger --- rss_reader/logger/tests/test_logger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 0d9c50bf..8cc84990 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -19,5 +19,10 @@ def test_null_config(): class TestLogger(): def test_setup_logger(self, logger_obj): + """Check return type of setup_logger function. + + :param logger_obj: Logger from the rss-reader.logger package. + :type logger_obj: Logger + """ o = logger_obj.setup_logger() assert isinstance(o, Logger) From cc106e8818e8db8a9430647cbfedd8e1037cc0a8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:26:14 +0300 Subject: [PATCH 528/973] create conftest in the tests from the logger --- rss_reader/logger/tests/conftest.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/logger/tests/conftest.py diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py new file mode 100644 index 00000000..e69de29b From b33a9e7b2727090cb040b875377b50784d7434b8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:26:53 +0300 Subject: [PATCH 529/973] import pytest --- rss_reader/logger/tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index e69de29b..5871ed8e 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -0,0 +1 @@ +import pytest From 80a0991a3d494a29ca8b7dc1f0cb815f586a1e3d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:27:25 +0300 Subject: [PATCH 530/973] create the logger_obj --- rss_reader/logger/tests/conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index 5871ed8e..96cc70bb 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -1 +1,5 @@ import pytest + + +def logger_obj(request): + pass From e2d287117e616d70837ef0f2771dc8052da512a7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:28:13 +0300 Subject: [PATCH 531/973] return Logger object --- rss_reader/logger/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index 96cc70bb..14ef3b4f 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -2,4 +2,4 @@ def logger_obj(request): - pass + yield Logger('test_name', request.param()) From db599aaaa7d2c31647bb9f8389d012d68a5f96c5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:29:03 +0300 Subject: [PATCH 532/973] import Logger from the logger module --- rss_reader/logger/tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index 14ef3b4f..8e3573ad 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -1,5 +1,7 @@ import pytest +from ..logger import Logger + def logger_obj(request): yield Logger('test_name', request.param()) From afdc259b1f27bc2af0a224cc2adadb33d99b0e4b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:29:59 +0300 Subject: [PATCH 533/973] make the logger_obj method a fixture --- rss_reader/logger/tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index 8e3573ad..24ad17b2 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -3,5 +3,8 @@ from ..logger import Logger +@pytest.fixture(scope="class", + params=[StreamHandlerConfig, + NullHandlerConfig]) def logger_obj(request): yield Logger('test_name', request.param()) From e89a0c42134967762c5f7905ddb6fd4aa84eed06 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:30:34 +0300 Subject: [PATCH 534/973] import StreamHandlerConfig --- rss_reader/logger/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index 24ad17b2..45306399 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -1,6 +1,6 @@ import pytest -from ..logger import Logger +from ..logger import Logger, StreamHandlerConfig @pytest.fixture(scope="class", From 02272d9d216ba1cebceeee11633231c53379a161 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:31:14 +0300 Subject: [PATCH 535/973] import NullHandlerConfig --- rss_reader/logger/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index 45306399..fca913e6 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -1,6 +1,6 @@ import pytest -from ..logger import Logger, StreamHandlerConfig +from ..logger import Logger, StreamHandlerConfig, NullHandlerConfig @pytest.fixture(scope="class", From 5611b1210b0791e9e3b3dcd50828252ebb9f8832 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:34:00 +0300 Subject: [PATCH 536/973] add a dockstring to the logger_obj fixture --- rss_reader/logger/tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/logger/tests/conftest.py b/rss_reader/logger/tests/conftest.py index fca913e6..a922ba8f 100644 --- a/rss_reader/logger/tests/conftest.py +++ b/rss_reader/logger/tests/conftest.py @@ -7,4 +7,5 @@ params=[StreamHandlerConfig, NullHandlerConfig]) def logger_obj(request): + """The fixture returns a logger object with different configurations.""" yield Logger('test_name', request.param()) From 67926a598713f00979d684b31944364586e85178 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:34:41 +0300 Subject: [PATCH 537/973] create test_get_logger method --- rss_reader/logger/tests/test_logger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 8cc84990..912e91ed 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -26,3 +26,6 @@ def test_setup_logger(self, logger_obj): """ o = logger_obj.setup_logger() assert isinstance(o, Logger) + + def test_get_logger(self, logger_obj): + pass From 6f2814f7d60e9739eb0623668303f7cb4e5a6c30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:35:47 +0300 Subject: [PATCH 538/973] compare the type of the received logger with the required one --- rss_reader/logger/tests/test_logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 912e91ed..f2a84f96 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -28,4 +28,5 @@ def test_setup_logger(self, logger_obj): assert isinstance(o, Logger) def test_get_logger(self, logger_obj): - pass + o = logger_obj.get_logger('test_module') + assert isinstance(o, Logger) From 3bcdcddcb10aeb969219bbd06674cca63ac10e76 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:36:57 +0300 Subject: [PATCH 539/973] add a dockstring to the test_get_logger --- rss_reader/logger/tests/test_logger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index f2a84f96..3256c5fd 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -28,5 +28,10 @@ def test_setup_logger(self, logger_obj): assert isinstance(o, Logger) def test_get_logger(self, logger_obj): + """Check return type of get_logger function. + + :param logger_obj: Logger from the rss-reader.logger package. + :type logger_obj: Logger + """ o = logger_obj.get_logger('test_module') assert isinstance(o, Logger) From 66442ecce0e5ca763d8a82978169f25eb38865ca Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:48:50 +0300 Subject: [PATCH 540/973] add a dockstring to the test_logger module --- rss_reader/logger/tests/test_logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/logger/tests/test_logger.py b/rss_reader/logger/tests/test_logger.py index 3256c5fd..f02be8b5 100644 --- a/rss_reader/logger/tests/test_logger.py +++ b/rss_reader/logger/tests/test_logger.py @@ -1,3 +1,5 @@ +"""A test suite for the logger module.""" + from logging import Logger from ..logger import StreamHandlerConfig, NullHandlerConfig @@ -18,6 +20,8 @@ def test_null_config(): class TestLogger(): + """Class tests Logger class from rss-reader.""" + def test_setup_logger(self, logger_obj): """Check return type of setup_logger function. From 45cdaa7295558940f4052f91244c13b543cfe189 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:51:30 +0300 Subject: [PATCH 541/973] create tests ackage in the parser package --- rss_reader/parser/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/parser/tests/__init__.py diff --git a/rss_reader/parser/tests/__init__.py b/rss_reader/parser/tests/__init__.py new file mode 100644 index 00000000..e69de29b From 769f5dbcb275b4577f95c3cbcf162ed9c692be89 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:52:34 +0300 Subject: [PATCH 542/973] add a dockstring to the tests package fom the parser package --- rss_reader/parser/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/tests/__init__.py b/rss_reader/parser/tests/__init__.py index e69de29b..ac99e42b 100644 --- a/rss_reader/parser/tests/__init__.py +++ b/rss_reader/parser/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for the parser package.""" From b8fb0015cedcc2af1e06b5df4b3cea4184e5aa19 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sat, 25 Jun 2022 23:53:08 +0300 Subject: [PATCH 543/973] create test_parser module --- rss_reader/parser/tests/test_parser.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/parser/tests/test_parser.py diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py new file mode 100644 index 00000000..e69de29b From ab2a0a332daf9a7ab9aa73a0199bb3f2f2cba5ab Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:34:17 +0300 Subject: [PATCH 544/973] import pytest to the test_parser module --- rss_reader/parser/tests/test_parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index e69de29b..60022b92 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -0,0 +1,3 @@ + + +import pytest From 2cb270f528b2857ddd1223168b47c3b0d28fac7a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:35:19 +0300 Subject: [PATCH 545/973] import Generator from the "cpllections" to the test_parser --- rss_reader/parser/tests/test_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 60022b92..088969d0 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -1,3 +1,4 @@ import pytest +from collections.abc import Generator From 50de8899dd967307351459405e2a043f34054528 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:36:10 +0300 Subject: [PATCH 546/973] import BeautifulParser to the test_parser module --- rss_reader/parser/tests/test_parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 088969d0..ee1233aa 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -2,3 +2,6 @@ import pytest from collections.abc import Generator + + +from ..parser import BeautifulParser From 9118e70f799c3e47b35c4a9d50f39fc14318d8b9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:38:18 +0300 Subject: [PATCH 547/973] import EmptyListError to the test_parser --- rss_reader/parser/tests/test_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index ee1233aa..71d75994 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -5,3 +5,4 @@ from ..parser import BeautifulParser +from ..exceptions import EmptyListError From 45988cb1a9386e255f88dfbe6dfbc31c71a7b2fd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:47:18 +0300 Subject: [PATCH 548/973] create MockTags class into the test_parser --- rss_reader/parser/tests/test_parser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 71d75994..fbb3ab11 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -6,3 +6,10 @@ from ..parser import BeautifulParser from ..exceptions import EmptyListError + + +class MockTags: + + @property + def text(self): + return ['mock_1_text', 'mock_2_text'] From 2b7508fa26580e8cfb21796a43815460ea5770d6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:49:51 +0300 Subject: [PATCH 549/973] add a docstring to the MockTags class --- rss_reader/parser/tests/test_parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index fbb3ab11..cf44cbdf 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -9,6 +9,11 @@ class MockTags: + """A custom class. + + That will be a dummy return value will override BeautifulParser._select + called in BeautifulParser.get_tags_text. + """ @property def text(self): From adb45e9c28b1816512e157e17cfc6a9b9cc56fb8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:51:01 +0300 Subject: [PATCH 550/973] create test_get_tags_text_type test --- rss_reader/parser/tests/test_parser.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index cf44cbdf..75ddff0b 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -18,3 +18,13 @@ class MockTags: @property def text(self): return ['mock_1_text', 'mock_2_text'] + + +def test_get_tags_text_type(monkeypatch): + + def mock_tag(*args, **kwargs): + return MockTags + + monkeypatch.setattr(BeautifulParser, "_select", mock_tag) + title_ = BeautifulParser(object).get_tags_text('title') + assert isinstance(title_, Generator) From f820684ac35a450ffccae019b79ab728f0824d13 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:52:45 +0300 Subject: [PATCH 551/973] add a dockstring to the test_get_tags_text_type --- rss_reader/parser/tests/test_parser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 75ddff0b..9f9c970c 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -21,6 +21,10 @@ def text(self): def test_get_tags_text_type(monkeypatch): + """Check the type of the returned object. + + The type of the returned object is Generator. + """ def mock_tag(*args, **kwargs): return MockTags From a8a1a3769fc8c1832f6bb2aba2c2274f50cc56cb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:53:46 +0300 Subject: [PATCH 552/973] create the test_get_tags_text_EmptyListError test --- rss_reader/parser/tests/test_parser.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 9f9c970c..538443eb 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -32,3 +32,14 @@ def mock_tag(*args, **kwargs): monkeypatch.setattr(BeautifulParser, "_select", mock_tag) title_ = BeautifulParser(object).get_tags_text('title') assert isinstance(title_, Generator) + + +def test_get_tags_text_EmptyListError(monkeypatch): + + def mock_raise(*args, **kwargs): + return [] + + monkeypatch.setattr(BeautifulParser, "_select", mock_raise) + + with pytest.raises(EmptyListError): + next(BeautifulParser(object).get_tags_text('title')) From e8696a02142cd80cc09b1ddbfcccf9056fefa764 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:54:44 +0300 Subject: [PATCH 553/973] add a docstring to the test_get_tags_text_EmptyListError --- rss_reader/parser/tests/test_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 538443eb..e50849a8 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -35,6 +35,7 @@ def mock_tag(*args, **kwargs): def test_get_tags_text_EmptyListError(monkeypatch): + """подтвердить возникновение ошибки EmptyListError.""" def mock_raise(*args, **kwargs): return [] From 4b37b2254a2e384c16db3409acfdfb4c55ce7f88 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:56:24 +0300 Subject: [PATCH 554/973] create the test_get_tags_text_amount_of_elements test --- rss_reader/parser/tests/test_parser.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index e50849a8..f9587171 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -44,3 +44,17 @@ def mock_raise(*args, **kwargs): with pytest.raises(EmptyListError): next(BeautifulParser(object).get_tags_text('title')) + + +def test_get_tags_text_amount_of_elements(monkeypatch): + + def mock_tags(*args, **kwargs): + return [MockTags(), MockTags()] + + monkeypatch.setattr(BeautifulParser, "_select", mock_tags) + + result = BeautifulParser(object).get_tags_text('title') + count = 0 + for i in result: + count += 1 + assert count == 2 From 87817db06f1b4505d461f3443528f14b071fd335 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 06:57:09 +0300 Subject: [PATCH 555/973] add a dockstring to the test_get_tags_text_amount_of_elements --- rss_reader/parser/tests/test_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index f9587171..6f2c2c41 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -47,6 +47,7 @@ def mock_raise(*args, **kwargs): def test_get_tags_text_amount_of_elements(monkeypatch): + """Check that the correct number of elements is returned.""" def mock_tags(*args, **kwargs): return [MockTags(), MockTags()] From d007ef1e58c7f204d8b0ccc3a9ebf567f24d97fc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:46:40 +0300 Subject: [PATCH 556/973] create test_get_items_empty test --- rss_reader/parser/tests/test_parser.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 6f2c2c41..522b3496 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -59,3 +59,15 @@ def mock_tags(*args, **kwargs): for i in result: count += 1 assert count == 2 + + +def test_get_items_empty(monkeypatch): + + def mock_items(*args, **kwargs): + return [] + + monkeypatch.setattr(BeautifulParser, "_find_all", mock_items) + + result = BeautifulParser(object).get_items({}, 'title') + + assert bool(result) is False From efdbac3cfa9a5d483a877a14eb15ab1f78f2acfc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:47:37 +0300 Subject: [PATCH 557/973] add a dockstring to the test_get_items_empty test --- rss_reader/parser/tests/test_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 522b3496..71381fbe 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -62,7 +62,7 @@ def mock_tags(*args, **kwargs): def test_get_items_empty(monkeypatch): - + """Check that an empty dictionary is returned on an invalid tag""" def mock_items(*args, **kwargs): return [] From d0a79f21e6bb6acc3b05a82a07903a00d14075dd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:48:57 +0300 Subject: [PATCH 558/973] add a dockstring to the test_parser --- rss_reader/parser/tests/test_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/parser/tests/test_parser.py b/rss_reader/parser/tests/test_parser.py index 71381fbe..62eb78cc 100644 --- a/rss_reader/parser/tests/test_parser.py +++ b/rss_reader/parser/tests/test_parser.py @@ -1,4 +1,4 @@ - +"""A test suite for the parser module.""" import pytest from collections.abc import Generator From bb59aa290e0be913597b08daa3e1d6239bc004b7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:50:54 +0300 Subject: [PATCH 559/973] create tsts package in the starter package --- rss_reader/starter/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/starter/tests/__init__.py diff --git a/rss_reader/starter/tests/__init__.py b/rss_reader/starter/tests/__init__.py new file mode 100644 index 00000000..e69de29b From a0f0ad9f13b11a007bbbc28e70b4a35696ac47ed Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:52:22 +0300 Subject: [PATCH 560/973] add a dockstring to the test_starter module --- rss_reader/starter/tests/test_starter.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 rss_reader/starter/tests/test_starter.py diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py new file mode 100644 index 00000000..86a4a839 --- /dev/null +++ b/rss_reader/starter/tests/test_starter.py @@ -0,0 +1 @@ +"""A test suite for the starter module.""" From 6ad09114367cdc80a451248b01f2ff2989c20e7b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:53:14 +0300 Subject: [PATCH 561/973] add a dockstring to the test package in the starte package --- rss_reader/starter/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/tests/__init__.py b/rss_reader/starter/tests/__init__.py index e69de29b..4072792b 100644 --- a/rss_reader/starter/tests/__init__.py +++ b/rss_reader/starter/tests/__init__.py @@ -0,0 +1 @@ +"""A test suite for the parser package.""" From 94c2c6fca44c6c08c03dd7e14f003f4d7376370b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:54:06 +0300 Subject: [PATCH 562/973] import pytest.reises to the test_starter module --- rss_reader/starter/tests/test_starter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index 86a4a839..99defd77 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -1 +1,3 @@ """A test suite for the starter module.""" + +from pytest import raises From b3a09ebb0b091a25ef729724584fe9061f33d803 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:54:50 +0300 Subject: [PATCH 563/973] import Starter to the test_starter module --- rss_reader/starter/tests/test_starter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index 99defd77..c8127b08 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -1,3 +1,6 @@ """A test suite for the starter module.""" from pytest import raises + + +from ..starter import Starter From df87b66204e38dd00c44c0e0c41b135ec887f1a9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 07:55:54 +0300 Subject: [PATCH 564/973] import NonNumericError to the test_starter module --- rss_reader/starter/tests/test_starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index c8127b08..5d337756 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -4,3 +4,4 @@ from ..starter import Starter +from ..ecxeptions import NonNumericError From 9f45d0b3e8ce7462811da14045c6ac3254e3735b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:06:53 +0300 Subject: [PATCH 565/973] create the test_starter_run_NonNumericError test --- rss_reader/starter/tests/test_starter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index 5d337756..d662850e 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -5,3 +5,11 @@ from ..starter import Starter from ..ecxeptions import NonNumericError + + +def test_starter_run_NonNumericError(): + + argv = {'source': 1, 'limit': 'abc'} + s = Starter(argv) + with raises(NonNumericError): + s.run() From 96b5f66682fea03ec2fefc59f0b03befce37f609 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:07:50 +0300 Subject: [PATCH 566/973] add a dockstring to the test_starter_run_NonNumericError --- rss_reader/starter/tests/test_starter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index d662850e..fd098dac 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -8,6 +8,10 @@ def test_starter_run_NonNumericError(): + """Check NonNumericError exception return + + Occurs when --limit is not equal to a number. + """ argv = {'source': 1, 'limit': 'abc'} s = Starter(argv) From 5efab8207915e81850406595979371ac0bfe15dc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:13:17 +0300 Subject: [PATCH 567/973] create the test_starter_run_BadURLError test --- rss_reader/starter/tests/test_starter.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index fd098dac..1952d8c1 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -17,3 +17,20 @@ def test_starter_run_NonNumericError(): s = Starter(argv) with raises(NonNumericError): s.run() + + +def test_starter_run_BadURLError(monkeypatch): + + def mock_get_status(*args, **kwargs): + class Mock_BadURLError: + @classmethod + def get_data(self, a, b, c, d): + raise BadURLError + return Mock_BadURLError() + + monkeypatch.setattr(Starter, '_get_data_from_resource', mock_get_status) + + argv = {'source': 1, 'limit': 1} + s = Starter(argv) + with raises(BadURLError): + s.run() From 78575edb675aad92cd18fdb07ca9939d58cd2a42 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:14:18 +0300 Subject: [PATCH 568/973] add a dockstring to the test_starter_run_BadURLError --- rss_reader/starter/tests/test_starter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index 1952d8c1..2e6033e8 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -20,6 +20,10 @@ def test_starter_run_NonNumericError(): def test_starter_run_BadURLError(monkeypatch): + """Verify that the BadURLError exception is being caught. + + Occurs when an invalid URL is specified. + """ def mock_get_status(*args, **kwargs): class Mock_BadURLError: From 48e0f28ccdc6ca24009bf027b882e3db44452c9e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:15:05 +0300 Subject: [PATCH 569/973] create the BadURLError class in the test_starter module --- rss_reader/starter/tests/test_starter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index 2e6033e8..c4b1be19 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -7,6 +7,10 @@ from ..ecxeptions import NonNumericError +class BadURLError(Exception): + pass + + def test_starter_run_NonNumericError(): """Check NonNumericError exception return From 52118dad09e67df5b90d4b85fd86506fafa9616e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:16:20 +0300 Subject: [PATCH 570/973] add a dockstring to the BadURLError class --- rss_reader/starter/tests/test_starter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index c4b1be19..79e2438e 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -8,6 +8,7 @@ class BadURLError(Exception): + """Serves to simulate the occurrence of a BadURLError error.""" pass From 4c4327ae2865be10f760779ec0c831c0e08a974c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:21:04 +0300 Subject: [PATCH 571/973] create the test_base module --- rss_reader/starter/tests/test_base.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/starter/tests/test_base.py diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py new file mode 100644 index 00000000..e69de29b From 2740314c0d56feec2884aa60b095e2e98982850f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:21:46 +0300 Subject: [PATCH 572/973] add a d0ockstring to the test_base module --- rss_reader/starter/tests/test_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index e69de29b..d9232436 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -0,0 +1 @@ +"""A test suite for the base module.""" \ No newline at end of file From d612a4caa5343e6bc399b240be82c8ff15de0d49 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:30:43 +0300 Subject: [PATCH 573/973] import pytest to the test_base module --- rss_reader/starter/tests/test_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index d9232436..2ce96b76 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -1 +1,3 @@ -"""A test suite for the base module.""" \ No newline at end of file +"""A test suite for the base module.""" + +import pytest From 6b5002b4c3c1ac9d181b5793d5676285ee77515c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:31:15 +0300 Subject: [PATCH 574/973] import the init_arguments_functionality --- rss_reader/starter/tests/test_base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index 2ce96b76..4ec44b39 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -1,3 +1,5 @@ """A test suite for the base module.""" import pytest + +from ..base import init_arguments_functionality as iaf From 14dcac95484674b1a8e402ca64a783ac1a917aaf Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:32:08 +0300 Subject: [PATCH 575/973] create the test_init_arguments_functionality function --- rss_reader/starter/tests/test_base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index 4ec44b39..36023c36 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -3,3 +3,9 @@ import pytest from ..base import init_arguments_functionality as iaf + + +@pytest.mark.parametrize("option", [("https://y.com",)]) +def test_init_arguments_functionality(option): + a = iaf(option) + assert isinstance(a, dict) From 5c84a42dc33e86caa273879b903cf9d81a2e0ad1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:33:39 +0300 Subject: [PATCH 576/973] add a dockstring to the test_init_arguments_functionality --- rss_reader/starter/tests/test_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index 36023c36..c55c7e39 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -7,5 +7,6 @@ @pytest.mark.parametrize("option", [("https://y.com",)]) def test_init_arguments_functionality(option): + """Check if dictionary returned""" a = iaf(option) assert isinstance(a, dict) From 6b972f91bf17940168f427f25708dc24e6a40275 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:34:54 +0300 Subject: [PATCH 577/973] create the test_init_return_json function --- rss_reader/starter/tests/test_base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index c55c7e39..3c709d19 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -10,3 +10,9 @@ def test_init_arguments_functionality(option): """Check if dictionary returned""" a = iaf(option) assert isinstance(a, dict) + + +@pytest.mark.parametrize("option", [("https://y.com", "--json",)]) +def test_init_return_json(option): + a = iaf(option) + assert 'json' in a From 02de44db78a8c8fc04e22d735a8bb9bd149a1291 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:35:53 +0300 Subject: [PATCH 578/973] add a dockstring to the test_init_return_json --- rss_reader/starter/tests/test_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/starter/tests/test_base.py b/rss_reader/starter/tests/test_base.py index 3c709d19..8221965e 100644 --- a/rss_reader/starter/tests/test_base.py +++ b/rss_reader/starter/tests/test_base.py @@ -14,5 +14,6 @@ def test_init_arguments_functionality(option): @pytest.mark.parametrize("option", [("https://y.com", "--json",)]) def test_init_return_json(option): + """Check if a key exists in a dictionary.""" a = iaf(option) assert 'json' in a From 0c1874722680df0f30f3ff6eb39222cb86e3cc30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:37:35 +0300 Subject: [PATCH 579/973] create test package into the viewer module --- rss_reader/viewer/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/viewer/tests/__init__.py diff --git a/rss_reader/viewer/tests/__init__.py b/rss_reader/viewer/tests/__init__.py new file mode 100644 index 00000000..e69de29b From 5a6c0058bb4d6f0c6cc0647cf47f9743912f8f4b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:38:18 +0300 Subject: [PATCH 580/973] add a dockstring to the test package --- rss_reader/viewer/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/tests/__init__.py b/rss_reader/viewer/tests/__init__.py index e69de29b..1fc7cf93 100644 --- a/rss_reader/viewer/tests/__init__.py +++ b/rss_reader/viewer/tests/__init__.py @@ -0,0 +1 @@ +"""A test suite for the viewer package.""" From 8f21d5fdeae58dc4ba09c6f9d1a5a97b1d634c98 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 08:38:57 +0300 Subject: [PATCH 581/973] create test_viewer module --- rss_reader/viewer/tests/test_viewer.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/viewer/tests/test_viewer.py diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py new file mode 100644 index 00000000..e69de29b From 166923ca360ca3b4889616c40fdb27e6c05d0589 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:06:44 +0300 Subject: [PATCH 582/973] add a dockstring to the test_viewer module --- rss_reader/viewer/tests/test_viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index e69de29b..1aafc813 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -0,0 +1 @@ +"""A test suite for the viewer module.""" \ No newline at end of file From e7ea50b403f7996e6af68eb49f036720568a47ac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:07:33 +0300 Subject: [PATCH 583/973] import dumps to the test_viewer --- rss_reader/viewer/tests/test_viewer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 1aafc813..12d13f00 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -1 +1,3 @@ -"""A test suite for the viewer module.""" \ No newline at end of file +"""A test suite for the viewer module.""" + +from json import dumps From 0bd43332b9eeeca7176eed42d9614ec71a0dcb1c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:08:07 +0300 Subject: [PATCH 584/973] import JSONViewHandler --- rss_reader/viewer/tests/test_viewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 12d13f00..82a7a96d 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -1,3 +1,5 @@ """A test suite for the viewer module.""" from json import dumps + +from ..viewer import JSONViewHandler From bcb23910d8a800e7d3fb184dfc14f9aae32b7b10 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:08:49 +0300 Subject: [PATCH 585/973] import StandartViewHandler --- rss_reader/viewer/tests/test_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 82a7a96d..27336d40 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -2,4 +2,4 @@ from json import dumps -from ..viewer import JSONViewHandler +from ..viewer import JSONViewHandler, StandartViewHandler From 78008d644802751332f9f9202cf5b24eb5251548 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:11:44 +0300 Subject: [PATCH 586/973] create test_StandartViewHandler_show test --- rss_reader/viewer/tests/test_viewer.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 27336d40..f74ab34a 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -3,3 +3,13 @@ from json import dumps from ..viewer import JSONViewHandler, StandartViewHandler + + +def test_StandartViewHandler_show(capsys): + + data = {'title_web_resource': 'mock_show'} + json_obj = StandartViewHandler() + json_obj.show(data) + out, err = capsys.readouterr() + out = out.strip('\n') + assert out == 'Feed: : mock_show' From 85307eb14daa54faefa6cf055f50d1fe4eda178b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:13:39 +0300 Subject: [PATCH 587/973] add a docstring to the test_StandartViewHandler_show --- rss_reader/viewer/tests/test_viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index f74ab34a..1400847b 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -6,6 +6,7 @@ def test_StandartViewHandler_show(capsys): + """check that the show method prints the correct data to stdout.""" data = {'title_web_resource': 'mock_show'} json_obj = StandartViewHandler() From 1ef68eaabd197a05464f341060bd3a8c0154e981 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:15:20 +0300 Subject: [PATCH 588/973] create test_JSONViewHandler_show test --- rss_reader/viewer/tests/test_viewer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 1400847b..53268702 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -14,3 +14,14 @@ def test_StandartViewHandler_show(capsys): out, err = capsys.readouterr() out = out.strip('\n') assert out == 'Feed: : mock_show' + + +def test_JSONViewHandler_show(capsys): + + data = {'test_dict': 1} + json_obj = JSONViewHandler({'json': 1}) + json_obj.show(data) + out, err = capsys.readouterr() + out = out.strip('\n') + x = dumps(data, indent=3) + assert out == x \ No newline at end of file From 7e2a3cd5dd1cdd1e4f46f2caed1cb3387036ece6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:16:39 +0300 Subject: [PATCH 589/973] add a dockstring to the test_JSONViewHandler_show --- rss_reader/viewer/tests/test_viewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 53268702..5512b213 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -17,6 +17,7 @@ def test_StandartViewHandler_show(capsys): def test_JSONViewHandler_show(capsys): + """Check that the show method prints data to stdout in the format json.""" data = {'test_dict': 1} json_obj = JSONViewHandler({'json': 1}) @@ -24,4 +25,4 @@ def test_JSONViewHandler_show(capsys): out, err = capsys.readouterr() out = out.strip('\n') x = dumps(data, indent=3) - assert out == x \ No newline at end of file + assert out == x From 62ad144e9016a3f18cc75954308c395350dac5ed Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:17:42 +0300 Subject: [PATCH 590/973] create test_JSONViewHandler_chain_show test --- rss_reader/viewer/tests/test_viewer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 5512b213..6e6cea5d 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -26,3 +26,15 @@ def test_JSONViewHandler_show(capsys): out = out.strip('\n') x = dumps(data, indent=3) assert out == x + + +def test_JSONViewHandler_chain_show(mocker): + + m = __name__ + '.StandartViewHandler.show' + mock_show = mocker.patch(m) + data = {'test_dict': 1} + json_obj = JSONViewHandler({}) + stdout_ = StandartViewHandler() + json_obj.set_next(stdout_) + json_obj.show(data) + assert mock_show.called From b787e4a0a49e3ac3c0235c6692ceb9a40910aa29 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:18:46 +0300 Subject: [PATCH 591/973] add a dockstring to the test_JSONViewHandler_chain_show --- rss_reader/viewer/tests/test_viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 6e6cea5d..4c055f50 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -29,6 +29,7 @@ def test_JSONViewHandler_show(capsys): def test_JSONViewHandler_chain_show(mocker): + """Check that the show method is called in chain.""" m = __name__ + '.StandartViewHandler.show' mock_show = mocker.patch(m) From fe71975289bd23f5741233b4ea99e6482e5e740f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:22:15 +0300 Subject: [PATCH 592/973] install pytest-mock --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a22ffd7a..9d4b5a88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ pycodestyle==2.8.0 pyparsing==3.0.9 pytest==7.1.2 pytest-cov==3.0.0 +pytest-mock==3.8.1 requests==2.28.0 soupsieve==2.3.2.post1 toml==0.10.2 From 477b48e3727b7de891e55a42c7b2a64e42cc1d63 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 09:57:26 +0300 Subject: [PATCH 593/973] fix. replace _get_status with _get_response --- rss_reader/crawler/tests/test_super_crawler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rss_reader/crawler/tests/test_super_crawler.py b/rss_reader/crawler/tests/test_super_crawler.py index 68a5ec3d..4bda1b26 100644 --- a/rss_reader/crawler/tests/test_super_crawler.py +++ b/rss_reader/crawler/tests/test_super_crawler.py @@ -46,10 +46,10 @@ def test_get_fail_error(monkeypatch): def mock_get_status(*args, **kwargs): return MockResponse(b'', 100) - monkeypatch.setattr(SuperCrawler, '_get_status', mock_get_status) + monkeypatch.setattr(SuperCrawler, '_get_response', mock_get_status) with pytest.raises(FailStatusCodeError): - SuperCrawler('https://news.yahoo.com/rss/').get_data() + SuperCrawler('https://news.yahoo3.com/rss/').get_data() def test_fail_url_response(monkeypatch): From cbe793c48cea57b7781e52f779b42634d3e1a91a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:01:30 +0300 Subject: [PATCH 594/973] create tests package in the rss_reader --- rss_reader/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/tests/__init__.py diff --git a/rss_reader/tests/__init__.py b/rss_reader/tests/__init__.py new file mode 100644 index 00000000..e69de29b From f41d6f66efd82b441ba4f8b033d55c6bcba18565 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:07:30 +0300 Subject: [PATCH 595/973] add a doxkstring to the test package in the rss_reader --- rss_reader/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/tests/__init__.py b/rss_reader/tests/__init__.py index e69de29b..70da13d2 100644 --- a/rss_reader/tests/__init__.py +++ b/rss_reader/tests/__init__.py @@ -0,0 +1 @@ +"""Integration test package.""" From 38cdd9f28c7443e3de78d6b0cb1e956e483d2349 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:09:32 +0300 Subject: [PATCH 596/973] create loader package in the tests package --- rss_reader/tests/loader/__init__py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/tests/loader/__init__py diff --git a/rss_reader/tests/loader/__init__py b/rss_reader/tests/loader/__init__py new file mode 100644 index 00000000..e69de29b From 58cd09c8d248f0a5df8e307a0dfb1868fbd13f9d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:10:18 +0300 Subject: [PATCH 597/973] create loader module for the integration test --- rss_reader/tests/loader/loader.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/tests/loader/loader.py diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py new file mode 100644 index 00000000..e69de29b From f3690e121b73cd10d774ced12ab8fc918fac10c0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:11:32 +0300 Subject: [PATCH 598/973] add a dockstring to the loader test module --- rss_reader/tests/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index e69de29b..007575db 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -0,0 +1 @@ +"""A test suite for the loader module.""" \ No newline at end of file From 4c84ec927ee9ef7dd51f95f650915cd2b512b873 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:12:47 +0300 Subject: [PATCH 599/973] add a dockstring to the tests --- rss_reader/tests/loader/__init__.py | 1 + rss_reader/tests/loader/__init__py | 0 2 files changed, 1 insertion(+) create mode 100644 rss_reader/tests/loader/__init__.py delete mode 100644 rss_reader/tests/loader/__init__py diff --git a/rss_reader/tests/loader/__init__.py b/rss_reader/tests/loader/__init__.py new file mode 100644 index 00000000..e440e0d3 --- /dev/null +++ b/rss_reader/tests/loader/__init__.py @@ -0,0 +1 @@ +"""A test suite for the loader package.""" diff --git a/rss_reader/tests/loader/__init__py b/rss_reader/tests/loader/__init__py deleted file mode 100644 index e69de29b..00000000 From 0265ec25c5a73943dcdc343963e208cd5e5ef56a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:13:23 +0300 Subject: [PATCH 600/973] import pytest to the loader test module --- rss_reader/tests/loader/loader.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index 007575db..75698bb1 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -1 +1,3 @@ -"""A test suite for the loader module.""" \ No newline at end of file +"""A test suite for the loader module.""" + +import pytest From e0c1f59737a0b39ed749a121c9c9b54bee3e48df Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:16:11 +0300 Subject: [PATCH 601/973] import FromWebHandler to the loader test module --- rss_reader/tests/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index 75698bb1..42864071 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -1,3 +1,5 @@ """A test suite for the loader module.""" import pytest + +from rss_reader.loader.loader import FromWebHandler From f595f698a86b063a9ac7ac0d1046795db77961cc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:17:04 +0300 Subject: [PATCH 602/973] import SuperCrawler into the loader tests module --- rss_reader/tests/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index 42864071..ff11d729 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -3,3 +3,4 @@ import pytest from rss_reader.loader.loader import FromWebHandler +from rss_reader.crawler.crawler import SuperCrawler From 7456b19c60cfa2cd3c7d531c4993b123c8c9c522 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:17:42 +0300 Subject: [PATCH 603/973] import BeautifulParser into the loader tests module --- rss_reader/tests/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index ff11d729..9ea3eb30 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -4,3 +4,4 @@ from rss_reader.loader.loader import FromWebHandler from rss_reader.crawler.crawler import SuperCrawler +from rss_reader.parser.parser import BeautifulParser From f986b6a74e2822675756d7aca03b5044d79299e9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:38:16 +0300 Subject: [PATCH 604/973] create mock_crawler fixture --- rss_reader/tests/loader/loader.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index 9ea3eb30..b22aaee9 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -5,3 +5,11 @@ from rss_reader.loader.loader import FromWebHandler from rss_reader.crawler.crawler import SuperCrawler from rss_reader.parser.parser import BeautifulParser + + +@pytest.fixture +def mock_crawler(mocker): + m = __name__ + '.SuperCrawler' + mock_cls = mocker.patch(m) + mock_cp = mock_cls.return_value + mock_cp.get_data.return_value = "Response!" From 5dd850f1d9507b0831c17cf13ab3fc365db6549b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:39:05 +0300 Subject: [PATCH 605/973] add a docstring to the mock_crawler fixture --- rss_reader/tests/loader/loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index b22aaee9..107bfa4c 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -9,6 +9,8 @@ @pytest.fixture def mock_crawler(mocker): + """Replaces crawler methods""" + m = __name__ + '.SuperCrawler' mock_cls = mocker.patch(m) mock_cp = mock_cls.return_value From ded74231dd8ca4437e97c85d2785cb2e5c15323c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:39:45 +0300 Subject: [PATCH 606/973] create the mock_parser fixture --- rss_reader/tests/loader/loader.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index 107bfa4c..9a559379 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -15,3 +15,14 @@ def mock_crawler(mocker): mock_cls = mocker.patch(m) mock_cp = mock_cls.return_value mock_cp.get_data.return_value = "Response!" + + +@pytest.fixture +def mock_parser(mocker): + + m = __name__ + '.BeautifulParser' + mock_cls = mocker.patch(m) + mock_cp = mock_cls.return_value + mock_cp.create_parser.return_value = "Create!" + mock_cp.get_tags_text.return_value = iter(["Title!"]) + mock_cp.get_items.return_value = [{'mock_item': 1}] From d2a109996630aa5c2d31d1ade43c07572e97df25 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:40:23 +0300 Subject: [PATCH 607/973] add a docstring to the mock_parser fixture --- rss_reader/tests/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index 9a559379..d954e17b 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -19,6 +19,7 @@ def mock_crawler(mocker): @pytest.fixture def mock_parser(mocker): + """Replaces parser methods""" m = __name__ + '.BeautifulParser' mock_cls = mocker.patch(m) From abb1ae2a28858c6c393f941f3d238126faf0a12b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:42:09 +0300 Subject: [PATCH 608/973] create test_get_items_empty test --- rss_reader/tests/loader/loader.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index d954e17b..a227913c 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -27,3 +27,11 @@ def mock_parser(mocker): mock_cp.create_parser.return_value = "Create!" mock_cp.get_tags_text.return_value = iter(["Title!"]) mock_cp.get_items.return_value = [{'mock_item': 1}] + + +def test_get_items_empty(mock_crawler, mock_parser): + + h = FromWebHandler(SuperCrawler, BeautifulParser('s')) + r = h.get_data('a', 'b', 'c', 1) + assert r == {'title_web_resource': 'Title!', + 'items': [{'mock_item': 1}]} From dae66ab2ba372ad0b1d79bdb528c42d1b573d280 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:43:09 +0300 Subject: [PATCH 609/973] add a dockstring to the def test_get_items_empy test --- rss_reader/tests/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/loader.py index a227913c..e066bdbd 100644 --- a/rss_reader/tests/loader/loader.py +++ b/rss_reader/tests/loader/loader.py @@ -30,6 +30,7 @@ def mock_parser(mocker): def test_get_items_empty(mock_crawler, mock_parser): + """Check that a dictionary of the given structure is returned""" h = FromWebHandler(SuperCrawler, BeautifulParser('s')) r = h.get_data('a', 'b', 'c', 1) From 6ee28cc275b1ff6f7cbdd731d24d51fd763c780a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 10:44:44 +0300 Subject: [PATCH 610/973] rename loader to test_loader in the rss_reader.tests --- rss_reader/tests/loader/{loader.py => test_loader.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rss_reader/tests/loader/{loader.py => test_loader.py} (100%) diff --git a/rss_reader/tests/loader/loader.py b/rss_reader/tests/loader/test_loader.py similarity index 100% rename from rss_reader/tests/loader/loader.py rename to rss_reader/tests/loader/test_loader.py From 3bcd2772359ab27ee14eac30c0fb28192c56bf08 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 12:32:18 +0300 Subject: [PATCH 611/973] del default value from the argparse --- rss_reader/starter/base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 09f4f57d..f08b66cf 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -32,8 +32,6 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: ) parser.add_argument('source', - nargs='?', - default='https://news.yahoo.com/rss/', help='RSS URL') parser.add_argument('--version', From b771f9d3589140707e1446f3cedf7c4d232f5245 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 12:42:21 +0300 Subject: [PATCH 612/973] install sphinx --- requirements.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/requirements.txt b/requirements.txt index 9d4b5a88..ed72621b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,44 @@ +alabaster==0.7.12 atomicwrites==1.4.0 attrs==21.4.0 autopep8==1.6.0 +Babel==2.10.3 beautifulsoup4==4.11.1 certifi==2022.6.15 charset-normalizer==2.0.12 colorama==0.4.5 coverage==6.4.1 +docutils==0.18.1 idna==3.3 +imagesize==1.3.0 +importlib-metadata==4.12.0 iniconfig==1.1.1 +Jinja2==3.1.2 lxml==4.9.0 +MarkupSafe==2.1.1 packaging==21.3 pluggy==1.0.0 py==1.11.0 pycodestyle==2.8.0 +Pygments==2.12.0 pyparsing==3.0.9 pytest==7.1.2 pytest-cov==3.0.0 pytest-mock==3.8.1 +pytz==2022.1 requests==2.28.0 +snowballstemmer==2.2.0 soupsieve==2.3.2.post1 +Sphinx==5.0.2 +sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 toml==0.10.2 tomli==2.0.1 types-requests==2.27.31 types-urllib3==1.26.15 urllib3==1.26.9 +zipp==3.8.0 From aba1ef20dd10325b0c9f26f071781ba0ff941f46 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 12:50:29 +0300 Subject: [PATCH 613/973] run sphinx-quickstart --- docs/Makefile | 20 ++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++ docs/source/conf.py | 55 +++++++++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 20 ++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..dc1312ab --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..57d432eb --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,55 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'rss-reader' +copyright = '2022, Andrey Ozerets' +author = 'Andrey Ozerets' + +# The full version, including alpha/beta/rc tags +release = '0.0.1' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..1068d9b0 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,20 @@ +.. rss-reader documentation master file, created by + sphinx-quickstart on Sun Jun 26 12:48:42 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to rss-reader's documentation! +====================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From aec34e556546cf817fa94dcaad0c12b75fe61c70 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 12:57:23 +0300 Subject: [PATCH 614/973] set source code search path in the conf.py --- docs/source/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 57d432eb..82ef0095 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,9 +10,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) # -- Project information ----------------------------------------------------- From 63d7e02befcd312db2e60f283fa06414e758ea6f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 13:04:08 +0300 Subject: [PATCH 615/973] set extensions to the conf.py --- docs/source/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 82ef0095..bd860633 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,6 +31,9 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', ] # Add any paths that contain templates here, relative to this directory. @@ -52,4 +55,4 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ['_static'] From 10a2c8895da480cb38faf84bda69ae85a2fdb379 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:00:52 +0300 Subject: [PATCH 616/973] add start_program.rst to the index.rst --- docs/source/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 1068d9b0..824ccf23 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,6 +10,8 @@ Welcome to rss-reader's documentation! :maxdepth: 2 :caption: Contents: + start_program.rst + Indices and tables From 589bed8db5afd7c5beb811ab210d1c203eb62846 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:02:08 +0300 Subject: [PATCH 617/973] create start_program.rst --- docs/source/start_program.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/start_program.rst diff --git a/docs/source/start_program.rst b/docs/source/start_program.rst new file mode 100644 index 00000000..e69de29b From 74f25336779beb8eb4e6a7fcacc99475b90ae058 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:04:23 +0300 Subject: [PATCH 618/973] create information about starting the program --- docs/source/start_program.rst | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/source/start_program.rst b/docs/source/start_program.rst index e69de29b..61ffd79e 100644 --- a/docs/source/start_program.rst +++ b/docs/source/start_program.rst @@ -0,0 +1,72 @@ +Program launch +============== + +This part of the documentation describes how to run rss-reader. +--------------------------------------------------------------- + +To start rss-reader, just run this simple command in the terminal of your choice:: + + $ python -m rss_reader + +The program supports the following keys: + * source - RSS URL. Required argument. + * \-\-verbose - Outputs verbose status messages.a + * \-\-json - Print result as JSON in stdout. + * \-\-limit - Limit news topics if this parameter provided. + * \-\-version - Print version info. + + +Information display: +-------------------- +Normally. +~~~~~~~~~~~ + * When all data is available. + + .. figure:: /images/output.jpg + + * When there is missing data. + + .. figure:: /images/output-empty.jpg + + +With the given \-\-json parameter: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * When all data is available. + + .. code-block:: JSON + + { + 'title_web_resource': 'Yahoo News - Latest News & Headlines', + 'items': + [ + { + 'title': 'Wisconsin election investigator says he deleted records', + 'link': 'https://news.yahoo.com/wisconsin999.html', + 'pubDate': '2022-06-23T15:52:47Z', + 'source': 'Associated Press', + 'content': { + 'url':'https://s.yimg.com/uu/api5f67f7c68', + 'title': None} + } + ] + } + + * When there is missing data. + + .. code-block:: JSON + + { + 'title_web_resource': 'Yahoo News - Latest News & Headlines', + 'items': + [ + { + 'title': 'Wisconsin election investigator says he deleted records', + 'link': '', + 'pubDate': '2022-06-23T15:52:47Z', + 'source': '', + 'content': { + 'url':'', + 'title': None} + } + ] + } From 21b15694d8dbaf21cedd3f575e08ce43917f5a35 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:05:53 +0300 Subject: [PATCH 619/973] create pictures showing how the information is displayed --- docs/source/images/output-empty.jpg | Bin 0 -> 52940 bytes docs/source/images/output.jpg | Bin 0 -> 45608 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/images/output-empty.jpg create mode 100644 docs/source/images/output.jpg diff --git a/docs/source/images/output-empty.jpg b/docs/source/images/output-empty.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c93c8641c6a42588d04079856711b45fe7faef51 GIT binary patch literal 52940 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 E1>N7mz5oCK literal 0 HcmV?d00001 diff --git a/docs/source/images/output.jpg b/docs/source/images/output.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9482f25d77ec82a93bc1b773f4c9b6d65f3c1b02 GIT binary patch literal 45608 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 literal 0 HcmV?d00001 From dec2cb916d890a63abe902864444abadc7ad64b1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:09:08 +0300 Subject: [PATCH 620/973] add sphinx files to ignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 67c7af5a..999e9dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ .vscode .idea .coverage -__pycache__ \ No newline at end of file +__pycache__ +build +source/_templates +source/_static \ No newline at end of file From ce492640b07119106ab42e34eb96808ee29f5472 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:12:13 +0300 Subject: [PATCH 621/973] create test.rst file --- docs/source/tests.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/tests.rst diff --git a/docs/source/tests.rst b/docs/source/tests.rst new file mode 100644 index 00000000..e69de29b From 24d11222b5b10239c9d76e6dd6e39a304e2e744e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:24:10 +0300 Subject: [PATCH 622/973] create help for running tests --- docs/source/tests.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/source/tests.rst b/docs/source/tests.rst index e69de29b..69810ddf 100644 --- a/docs/source/tests.rst +++ b/docs/source/tests.rst @@ -0,0 +1,14 @@ +Program testing. +================ + +This part of the documentation describes how to test the rss-reader. +-------------------------------------------------------------------- + +To start testing just run the following code:: + + $ python -m pytest + +For reference: + +* Each package contains its own tests. +* Integration tests are collected in the rss_reader\tests package. \ No newline at end of file From c7a79e5701eff17352d623f25be26f2b5fd893d7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 17:24:49 +0300 Subject: [PATCH 623/973] add the test.rst to the index.rst --- docs/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 824ccf23..5d39d604 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,6 +11,7 @@ Welcome to rss-reader's documentation! :caption: Contents: start_program.rst + tests.rst From b07f5b4870141f46884733ea33c5a2752c11a73f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 18:27:37 +0300 Subject: [PATCH 624/973] install setuptools --- requirements.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/requirements.txt b/requirements.txt index ed72621b..f17d4249 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ alabaster==0.7.12 +appdirs==1.4.4 atomicwrites==1.4.0 attrs==21.4.0 autopep8==1.6.0 @@ -9,6 +10,7 @@ charset-normalizer==2.0.12 colorama==0.4.5 coverage==6.4.1 docutils==0.18.1 +esbonio==0.13.0 idna==3.3 imagesize==1.3.0 importlib-metadata==4.12.0 @@ -20,8 +22,11 @@ packaging==21.3 pluggy==1.0.0 py==1.11.0 pycodestyle==2.8.0 +pydantic==1.8.2 +pygls==0.11.3 Pygments==2.12.0 pyparsing==3.0.9 +pyspellchecker==0.6.3 pytest==7.1.2 pytest-cov==3.0.0 pytest-mock==3.8.1 @@ -38,7 +43,9 @@ sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 toml==0.10.2 tomli==2.0.1 +typeguard==2.13.3 types-requests==2.27.31 types-urllib3==1.26.15 +typing-extensions==4.2.0 urllib3==1.26.9 zipp==3.8.0 From 9abb07a6e74484dd8e4750dd606f38ce9b46aaef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 18:44:32 +0300 Subject: [PATCH 625/973] create README.md file --- README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..e69de29b From 89aae8bc861a65276b8d58212210a792c824c4f2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 19:32:35 +0300 Subject: [PATCH 626/973] add project description to README.md --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index e69de29b..05ae0432 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,21 @@ +# rss-reader + +## rss-reader is a command line utility that allows you to view RSS feeds + +## Usage + +``` +> python -m rss-reader https://news.yahoo.com/rss/ +``` +or: +``` +> rss-reader https://news.yahoo.com/rss/ +``` + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. + +## License +[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file From d44ea949885527426d488ae74b3de145ddab401b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 19:33:49 +0300 Subject: [PATCH 627/973] create setup.py file --- setup.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..e69de29b From 65ebcbe9942173ebefb1bc16bfa1d43569ae018d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 19:36:53 +0300 Subject: [PATCH 628/973] set project metadata in the setup.py file --- setup.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/setup.py b/setup.py index e69de29b..d761b4f8 100644 --- a/setup.py +++ b/setup.py @@ -0,0 +1,32 @@ +# import setuptools +from setuptools import setup, find_packages + +with open("README.md") as file: + read_me_description = file.read() + +setup( + name="rss_reader", + version="0.0.2", + author="Andrey Ozerets", + description="Super rss-reader.", + long_description=read_me_description, + long_description_content_type="text/markdown", + entry_points={ + 'console_scripts': [ + 'rss_reader = rss_reader.__main__:main', + ] + }, + install_requires=[ + "requests==2.28.0", + "beautifulsoup4 == 4.11.1", + "lxml==4.9.0", + ], + # packages=['FT'], + packages=find_packages(exclude=['test*']), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.8', +) From 24c121c34daef33b5db6b276ec53449f639d0a83 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 19:38:05 +0300 Subject: [PATCH 629/973] change project version to 0.0.2 --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index f08b66cf..5ff27494 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -13,7 +13,7 @@ NAME_LOGGER = 'rss-reader' -version = '0.0.1' +version = '0.0.2' def init_arguments_functionality(args=None) -> Dict[str, str]: From 75848fc9ecbfc58bab74a4feab1cc80654aff8d3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 19:39:45 +0300 Subject: [PATCH 630/973] chenge from build to build/ --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 999e9dfa..fa6b6e51 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ .idea .coverage __pycache__ -build +build/ source/_templates source/_static \ No newline at end of file From f7a6b301eef6105be7912829dd8877e63cc8bc63 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 19:41:01 +0300 Subject: [PATCH 631/973] exclude files for setuptools --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fa6b6e51..3de56af9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ __pycache__ build/ source/_templates -source/_static \ No newline at end of file +source/_static +docs/build +rss_reader.egg-info/ +dist/ \ No newline at end of file From 45168e20c6ea081fddd2763f65d0881f147a8da5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:14:54 +0300 Subject: [PATCH 632/973] change the program version to 0.0.3 --- rss_reader/starter/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 5ff27494..922757de 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -13,7 +13,7 @@ NAME_LOGGER = 'rss-reader' -version = '0.0.2' +version = '0.0.3' def init_arguments_functionality(args=None) -> Dict[str, str]: From 6887305f1f5dd82375c996ee239e09b789b8b182 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:18:06 +0300 Subject: [PATCH 633/973] add an optional parameter --date to the command line --- rss_reader/starter/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 922757de..3f8ba9e2 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -50,6 +50,11 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('--limit', help='Limit news topics if this parameter provided') + parser.add_argument('--date', + help='Search for news on a specified date.\ + Date in the format Y-m-d (for example: 20191206).' + ) + namespace_ = parser.parse_args(args) return vars(namespace_) From dab01c6685137873b6fa30b6ede2465459c63010 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:19:47 +0300 Subject: [PATCH 634/973] add debugging defaults to source --- rss_reader/starter/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 3f8ba9e2..84de663a 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -32,6 +32,8 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: ) parser.add_argument('source', + nargs='?', + default='https://news.yahoo.com/rss/', help='RSS URL') parser.add_argument('--version', From 8416f5316589c5f2145d72f3ad0b6a647ed19c2f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:34:10 +0300 Subject: [PATCH 635/973] create a saver package --- rss_reader/saver/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/saver/__init__.py diff --git a/rss_reader/saver/__init__.py b/rss_reader/saver/__init__.py new file mode 100644 index 00000000..e69de29b From b7b0c1dbad945722793f8931ce761dedb779f22e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:34:56 +0300 Subject: [PATCH 636/973] add a dockstring to the saver package --- rss_reader/saver/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/__init__.py b/rss_reader/saver/__init__.py index e69de29b..6be20347 100644 --- a/rss_reader/saver/__init__.py +++ b/rss_reader/saver/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with savers.""" From 125dc2f7840aa6f28e4caafc1bbe678ee503dcef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:37:47 +0300 Subject: [PATCH 637/973] create saver module --- rss_reader/saver/saver.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/saver/saver.py diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py new file mode 100644 index 00000000..e69de29b From 536723a594054d0dc5177052a34558a18c888d70 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:39:44 +0300 Subject: [PATCH 638/973] create isaver package --- rss_reader/interfaces/isaver/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/isaver/__init__.py diff --git a/rss_reader/interfaces/isaver/__init__.py b/rss_reader/interfaces/isaver/__init__.py new file mode 100644 index 00000000..e69de29b From fdbc998ff20dcc69ff3937ab0b6990b379fff14d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:40:23 +0300 Subject: [PATCH 639/973] add a dockstring to the isaver package --- rss_reader/interfaces/isaver/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/isaver/__init__.py b/rss_reader/interfaces/isaver/__init__.py index e69de29b..fb61ecaa 100644 --- a/rss_reader/interfaces/isaver/__init__.py +++ b/rss_reader/interfaces/isaver/__init__.py @@ -0,0 +1 @@ +"""This package contains the saver interfaces.""" From f587e3fd59415dd8bf14b8a93274dfa94bb284ed Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:41:02 +0300 Subject: [PATCH 640/973] create isaver module --- rss_reader/interfaces/isaver/isaver.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/isaver/isaver.py diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py new file mode 100644 index 00000000..e69de29b From 41e9d3404f4945793a5c38bc6639aa419a9b8496 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:41:58 +0300 Subject: [PATCH 641/973] import annotations to the isaver module --- rss_reader/interfaces/isaver/isaver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index e69de29b..9d48db4f 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -0,0 +1 @@ +from __future__ import annotations From 0805975a7dbc840572becdd945e12fbc325c03e1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:42:19 +0300 Subject: [PATCH 642/973] import ABC to the isaver module --- rss_reader/interfaces/isaver/isaver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 9d48db4f..15a0cc05 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -1 +1,2 @@ from __future__ import annotations +from abc import ABC From f72f23301e610840656bc83eb8afe4ddaa8f0082 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:42:38 +0300 Subject: [PATCH 643/973] import abstractmethod to the isaver module --- rss_reader/interfaces/isaver/isaver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 15a0cc05..62fef353 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -1,2 +1,2 @@ from __future__ import annotations -from abc import ABC +from abc import ABC, abstractmethod From ebe4418004de5b3d920d09f6bce694a0337b08d6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:43:10 +0300 Subject: [PATCH 644/973] create ISaveHandler class --- rss_reader/interfaces/isaver/isaver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 62fef353..57b4b6e7 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -1,2 +1,6 @@ from __future__ import annotations from abc import ABC, abstractmethod + + +class ISaveHandler(ABC): + pass From 7243ace82dca444684969cce4093340b8c61b788 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 22:59:46 +0300 Subject: [PATCH 645/973] create save method in the ISaveHandler --- rss_reader/interfaces/isaver/isaver.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 57b4b6e7..a947a961 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -3,4 +3,9 @@ class ISaveHandler(ABC): - pass + @abstractmethod + def set_next(self, handler: ISaveHandler) -> ISaveHandler: + pass + + def save(self, data: dict): + pass From 8c5f9dda4c1725946e848a8653aea75d6f6d338a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:01:56 +0300 Subject: [PATCH 646/973] add a dockstring to the set_next method --- rss_reader/interfaces/isaver/isaver.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index a947a961..15fe624d 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -5,6 +5,14 @@ class ISaveHandler(ABC): @abstractmethod def set_next(self, handler: ISaveHandler) -> ISaveHandler: + """Set the next saver in the handler chain. + + :param handler: Next handler. + :type handler: ISaveHandler + :return: Handler. + :rtype: ISaveHandler + """ + pass def save(self, data: dict): From 4908fc9ffe7f092e3ab77b7f7e96ff719487b3b4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:05:07 +0300 Subject: [PATCH 647/973] add a dockstring to the save method --- rss_reader/interfaces/isaver/isaver.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 15fe624d..954175cf 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -16,4 +16,9 @@ def set_next(self, handler: ISaveHandler) -> ISaveHandler: pass def save(self, data: dict): + """Save data. + + :param data: Dictionary with data to save. + :type data: dict + """ pass From 790e4b41ac392a8eb327c68df97028dc43a6ae2d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:06:01 +0300 Subject: [PATCH 648/973] add a dockstring to the isaver module --- rss_reader/interfaces/isaver/isaver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 954175cf..f67d6d80 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -1,3 +1,5 @@ +"""This module contains a set of interfaces for savers.""" + from __future__ import annotations from abc import ABC, abstractmethod From 630772b535bdecc0cc1869b8d24f7e3d7ed8eb9c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:07:11 +0300 Subject: [PATCH 649/973] add a docstring to the ISaveHandler class --- rss_reader/interfaces/isaver/isaver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index f67d6d80..1ed20bc3 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -5,6 +5,8 @@ class ISaveHandler(ABC): + """Basic interface of data savers.""" + @abstractmethod def set_next(self, handler: ISaveHandler) -> ISaveHandler: """Set the next saver in the handler chain. From f06963b0e1d265122070cb91406acef5ce113b35 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:08:54 +0300 Subject: [PATCH 650/973] create AbstractSaveHandler class --- rss_reader/saver/saver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index e69de29b..d29a5a4c 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -0,0 +1,4 @@ + + +class AbstractSaveHandler(ISaveHandler): + pass From 91bbc0603d1da0636c08c9559b14f26369e4763e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:09:35 +0300 Subject: [PATCH 651/973] create _next_handler variable --- rss_reader/saver/saver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index d29a5a4c..0206215c 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -1,4 +1,5 @@ class AbstractSaveHandler(ISaveHandler): - pass + + _next_handler: Optional[ISaveHandler] = None From f110d3ffdd32a23bf39936322be6e6f65810dc01 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:10:18 +0300 Subject: [PATCH 652/973] override set_next in the AbstractSaveHandler --- rss_reader/saver/saver.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 0206215c..163132b3 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -1,5 +1,10 @@ class AbstractSaveHandler(ISaveHandler): - + _next_handler: Optional[ISaveHandler] = None + + @send_log_of_start_function + def set_next(self, handler: ISaveHandler) -> ISaveHandler: + self._next_handler = handler + return handler From e5fdd0e8a6047dc6f7042ff8933eeaa41a50e39d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:10:41 +0300 Subject: [PATCH 653/973] override save in the AbstractSaveHandler --- rss_reader/saver/saver.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 163132b3..ad9d67cc 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -8,3 +8,8 @@ class AbstractSaveHandler(ISaveHandler): def set_next(self, handler: ISaveHandler) -> ISaveHandler: self._next_handler = handler return handler + + @send_log_of_start_function + def save(self, data: dict, file: str): + if self._next_handler: + return self._next_handler.show(data) From 0d20f777f64786c29fa25f8112f7314ffd4caabe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:11:52 +0300 Subject: [PATCH 654/973] import ISaveHandler to the saver module --- rss_reader/saver/saver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index ad9d67cc..f3a39b1f 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -1,4 +1,6 @@ +from rss_reader.interfaces.isaver.isaver import ISaveHandler + class AbstractSaveHandler(ISaveHandler): From 7b78d446d1250e59a52e669422e995823b78ff43 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:12:21 +0300 Subject: [PATCH 655/973] import Optional to the saver module --- rss_reader/saver/saver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index f3a39b1f..37bdd24c 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -1,4 +1,6 @@ +from typing import Optional + from rss_reader.interfaces.isaver.isaver import ISaveHandler From fab7ca2ebec0663955f169556049a429766f2d45 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:13:58 +0300 Subject: [PATCH 656/973] import send_log_of_start_function to the saver module --- rss_reader/saver/saver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 37bdd24c..4615a9f8 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -2,6 +2,7 @@ from typing import Optional from rss_reader.interfaces.isaver.isaver import ISaveHandler +from rss_reader.decorator.decorator import send_log_of_start_function class AbstractSaveHandler(ISaveHandler): From 5e7e4d2a5d252f65dfc606ec8489ce55c3b4e783 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:14:53 +0300 Subject: [PATCH 657/973] add a dockstring to the set_next in the AbstractSaveHandler --- rss_reader/saver/saver.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 4615a9f8..fc228bd6 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -11,6 +11,13 @@ class AbstractSaveHandler(ISaveHandler): @send_log_of_start_function def set_next(self, handler: ISaveHandler) -> ISaveHandler: + """Set the next saver in the handler chain. + + :param handler: Next handler. + :type handler: ISaveHandler + :return: Handler. + :rtype: ISaveHandler + """ self._next_handler = handler return handler From 2be3693934b349f58bc1320a1c6061d347056509 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:17:19 +0300 Subject: [PATCH 658/973] add a dockstring to the save in the AbstractSaveHandler --- rss_reader/saver/saver.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index fc228bd6..3b2e2bae 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -22,6 +22,13 @@ def set_next(self, handler: ISaveHandler) -> ISaveHandler: return handler @send_log_of_start_function - def save(self, data: dict, file: str): + def save(self, data: dict, file: str) -> None: + """Save data. + + :param data: Dictionary with data to save. + :type data: dict + :param file: File for save. + :type file: str + """ if self._next_handler: return self._next_handler.show(data) From a99d6979c50ed7db707ec14479dba4975ef673d0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:20:41 +0300 Subject: [PATCH 659/973] fix. docstring in the save method in the isaver module --- rss_reader/interfaces/isaver/isaver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 1ed20bc3..5751e814 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -19,10 +19,12 @@ def set_next(self, handler: ISaveHandler) -> ISaveHandler: pass - def save(self, data: dict): + def save(self, data: dict, file: str) -> None: """Save data. :param data: Dictionary with data to save. :type data: dict + :param file: File for save. + :type file: str """ pass From eb3d61c1e909093c8538130f7006e4ddd7bfb3c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:30:45 +0300 Subject: [PATCH 660/973] change docstring --- rss_reader/saver/saver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 3b2e2bae..4cb39e7f 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -22,12 +22,12 @@ def set_next(self, handler: ISaveHandler) -> ISaveHandler: return handler @send_log_of_start_function - def save(self, data: dict, file: str) -> None: + def save(self, data: List[dict], file: str) -> None: """Save data. :param data: Dictionary with data to save. - :type data: dict - :param file: File for save. + :type data: List[dict] + :param file: File save path. :type file: str """ if self._next_handler: From a1cdbc6aa7b4825cc3a5b6ccaf754216150b688b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:31:09 +0300 Subject: [PATCH 661/973] change docstring --- rss_reader/interfaces/isaver/isaver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index 5751e814..cae0a28a 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -19,7 +19,7 @@ def set_next(self, handler: ISaveHandler) -> ISaveHandler: pass - def save(self, data: dict, file: str) -> None: + def save(self, data: List[dict], file: str) -> None: """Save data. :param data: Dictionary with data to save. From cc7ba970ded44d941a1563f9377575db0cbc3ea4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:31:46 +0300 Subject: [PATCH 662/973] import List --- rss_reader/saver/saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 4cb39e7f..216fbb86 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -1,5 +1,5 @@ -from typing import Optional +from typing import Optional, List from rss_reader.interfaces.isaver.isaver import ISaveHandler from rss_reader.decorator.decorator import send_log_of_start_function From 320743b2cd48cacb848649473aeb4ea6e8ca822a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Sun, 26 Jun 2022 23:32:48 +0300 Subject: [PATCH 663/973] import List --- rss_reader/interfaces/isaver/isaver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index cae0a28a..fe9319b7 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -2,6 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import List class ISaveHandler(ABC): From 3aa637b4778e6b2d32a80a056748345213f2c77e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 05:59:01 +0300 Subject: [PATCH 664/973] create LocalSaveHandler class --- rss_reader/saver/saver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 216fbb86..590e54e8 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -32,3 +32,7 @@ def save(self, data: List[dict], file: str) -> None: """ if self._next_handler: return self._next_handler.show(data) + + +class LocalSaveHandler(AbstractSaveHandler): + pass From 539670a0d4e98373d4e36e8e9667d4226735731f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:00:50 +0300 Subject: [PATCH 665/973] implement the save method in the LocalSaveHandler --- rss_reader/saver/saver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 590e54e8..04434ef1 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -35,4 +35,5 @@ def save(self, data: List[dict], file: str) -> None: class LocalSaveHandler(AbstractSaveHandler): - pass + def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: + pass From 189c3c2f5af0a45513ca9300d08c286f930ffba2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:10:52 +0300 Subject: [PATCH 666/973] create local_data variable --- rss_reader/saver/saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 04434ef1..003369c9 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -36,4 +36,4 @@ def save(self, data: List[dict], file: str) -> None: class LocalSaveHandler(AbstractSaveHandler): def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: - pass + local_data = ReaderFiles().read_csv_file(file, 'index', CreaterFiles()) From e908950ce42b5e520af76979119e81653acf9a3b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:13:10 +0300 Subject: [PATCH 667/973] get normalized data --- rss_reader/saver/saver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 003369c9..6c468a42 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -37,3 +37,5 @@ def save(self, data: List[dict], file: str) -> None: class LocalSaveHandler(AbstractSaveHandler): def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: local_data = ReaderFiles().read_csv_file(file, 'index', CreaterFiles()) + + norm_data = DataConverter().concat_data(data, local_data) \ No newline at end of file From 77062013fd97d477cbbaf59ee3ab5b34b794823a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:19:27 +0300 Subject: [PATCH 668/973] catch the NotImplementedError in the save method --- rss_reader/saver/saver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 6c468a42..286526c2 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -38,4 +38,8 @@ class LocalSaveHandler(AbstractSaveHandler): def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: local_data = ReaderFiles().read_csv_file(file, 'index', CreaterFiles()) - norm_data = DataConverter().concat_data(data, local_data) \ No newline at end of file + try: + norm_data = DataConverter().concat_data(data, local_data) + except NotImplementedError as e: + local_data = None + norm_data = pd.DataFrame() \ No newline at end of file From fb010083d7d7b5f683f21692c8d929e6bce4bb84 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:22:04 +0300 Subject: [PATCH 669/973] save data to csv file --- rss_reader/saver/saver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 286526c2..d1259257 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -42,4 +42,6 @@ def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: norm_data = DataConverter().concat_data(data, local_data) except NotImplementedError as e: local_data = None - norm_data = pd.DataFrame() \ No newline at end of file + norm_data = pd.DataFrame() + + norm_data.to_csv(file, encoding='utf-8', index_label='index') From 022ad5b303541467355478839ae35e871b2c4df2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:23:10 +0300 Subject: [PATCH 670/973] import pandas in to the saver module --- rss_reader/saver/saver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index d1259257..c02e7f1b 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -1,6 +1,8 @@ +import pandas as pd from typing import Optional, List + from rss_reader.interfaces.isaver.isaver import ISaveHandler from rss_reader.decorator.decorator import send_log_of_start_function From 7716f087801e0f8feee35fb9c95b863e97b864d2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:26:55 +0300 Subject: [PATCH 671/973] install pandas package --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requirements.txt b/requirements.txt index f17d4249..610b634a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,9 @@ iniconfig==1.1.1 Jinja2==3.1.2 lxml==4.9.0 MarkupSafe==2.1.1 +numpy==1.23.0 packaging==21.3 +pandas==1.4.3 pluggy==1.0.0 py==1.11.0 pycodestyle==2.8.0 @@ -30,8 +32,10 @@ pyspellchecker==0.6.3 pytest==7.1.2 pytest-cov==3.0.0 pytest-mock==3.8.1 +python-dateutil==2.8.2 pytz==2022.1 requests==2.28.0 +six==1.16.0 snowballstemmer==2.2.0 soupsieve==2.3.2.post1 Sphinx==5.0.2 From f304d37743b0b77e80a91485e7c3572c61315aff Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:34:13 +0300 Subject: [PATCH 672/973] create idataconverter package --- rss_reader/interfaces/idataconverter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/idataconverter/__init__.py diff --git a/rss_reader/interfaces/idataconverter/__init__.py b/rss_reader/interfaces/idataconverter/__init__.py new file mode 100644 index 00000000..e69de29b From 70f78689a79e9fab2c052c566d9462493a200ba8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:35:53 +0300 Subject: [PATCH 673/973] add a dockstring to the idataconverter package --- rss_reader/interfaces/idataconverter/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/idataconverter/__init__.py b/rss_reader/interfaces/idataconverter/__init__.py index e69de29b..c9f336a1 100644 --- a/rss_reader/interfaces/idataconverter/__init__.py +++ b/rss_reader/interfaces/idataconverter/__init__.py @@ -0,0 +1 @@ +"""This package contains the dataconverter interfaces.""" From 7fe0726893874eeaf73cef7eccba3dd546800d62 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:36:52 +0300 Subject: [PATCH 674/973] create IDataConverter class --- rss_reader/interfaces/idataconverter/idataconverter.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rss_reader/interfaces/idataconverter/idataconverter.py diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py new file mode 100644 index 00000000..134a5514 --- /dev/null +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -0,0 +1,4 @@ + + +class IDataConverter(ABC): + pass From 4b4bb8167f2cc864861ed89705578341891ed68a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:37:37 +0300 Subject: [PATCH 675/973] create concat_data method --- rss_reader/interfaces/idataconverter/idataconverter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 134a5514..d0b21e4a 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -1,4 +1,6 @@ class IDataConverter(ABC): - pass + @abstractmethod + def concat_data(self, data, local_data) -> Optional[DataFrame]: + pass From 39f02f03eee49684b8e9a888843350007ae5df5a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:38:18 +0300 Subject: [PATCH 676/973] import abstractmethod into the idataconverter --- rss_reader/interfaces/idataconverter/idataconverter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index d0b21e4a..1da77009 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -1,4 +1,6 @@ +from abc import abstractmethod + class IDataConverter(ABC): @abstractmethod From f3cc40bbb376428a7d692287fcc1b3b68bb810ec Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:38:43 +0300 Subject: [PATCH 677/973] import ABC into the idataconverter --- rss_reader/interfaces/idataconverter/idataconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 1da77009..7fc2eac6 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -1,5 +1,5 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC class IDataConverter(ABC): From ab68eae0e912867c99411361589bd7fd8ef47c69 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:40:02 +0300 Subject: [PATCH 678/973] import Optional into the idataconverter --- rss_reader/interfaces/idataconverter/idataconverter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 7fc2eac6..75619fbd 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -1,5 +1,6 @@ from abc import abstractmethod, ABC +from typing import Optional class IDataConverter(ABC): From f0c0f81880252da70f9dce22340e81f9122e27ac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:40:23 +0300 Subject: [PATCH 679/973] import DataFrame into the idataconverter --- rss_reader/interfaces/idataconverter/idataconverter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 75619fbd..ea8739c6 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -1,6 +1,7 @@ from abc import abstractmethod, ABC from typing import Optional +from pandas import DataFrame class IDataConverter(ABC): From 5073827916b85b6f0b11d60459b42386646f51bd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:42:04 +0300 Subject: [PATCH 680/973] add a dockstring to the idataconverter module --- rss_reader/interfaces/idataconverter/idataconverter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index ea8739c6..2c9d9021 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -1,3 +1,5 @@ +"""This module contains a set of interfaces for the logger.""" + from abc import abstractmethod, ABC from typing import Optional From c4d1453f83b0004e4a008780c774102bcf783553 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:52:26 +0300 Subject: [PATCH 681/973] add a dockstring to the IDataConverter --- rss_reader/interfaces/idataconverter/idataconverter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 2c9d9021..d22a36e0 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -7,6 +7,8 @@ class IDataConverter(ABC): + """Basic converter interface.""" + @abstractmethod def concat_data(self, data, local_data) -> Optional[DataFrame]: pass From 15f9ff0acdde506efce2223c84945c3936a874bb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:53:31 +0300 Subject: [PATCH 682/973] add a dockstring to the concat_data abstract method --- rss_reader/interfaces/idataconverter/idataconverter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index d22a36e0..5f0944b6 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -11,4 +11,5 @@ class IDataConverter(ABC): @abstractmethod def concat_data(self, data, local_data) -> Optional[DataFrame]: + """Get normalized data.""" pass From db89d88976957bb9f6b76199bc9b7fa271ec4a21 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:55:48 +0300 Subject: [PATCH 683/973] create data_converter package --- rss_reader/data_converter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/data_converter/__init__.py diff --git a/rss_reader/data_converter/__init__.py b/rss_reader/data_converter/__init__.py new file mode 100644 index 00000000..e69de29b From 93f9d93ad02a321dccba6d144198af996664c1f4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:56:43 +0300 Subject: [PATCH 684/973] add a dockstring to the data_converter package --- rss_reader/data_converter/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/data_converter/__init__.py b/rss_reader/data_converter/__init__.py index e69de29b..b6b0fcbb 100644 --- a/rss_reader/data_converter/__init__.py +++ b/rss_reader/data_converter/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with dat converters.""" From d43ca88c0c14a676decbc1c9ff34da3b01896a7d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:57:27 +0300 Subject: [PATCH 685/973] create data_converter module --- rss_reader/data_converter/data_converter.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/data_converter/data_converter.py diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py new file mode 100644 index 00000000..e69de29b From 5e1cf088b64de5310287d63f7e48e950e7ef8d57 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 06:58:55 +0300 Subject: [PATCH 686/973] create DataConverter class --- rss_reader/data_converter/data_converter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index e69de29b..193a0b90 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -0,0 +1,4 @@ + + +class DataConverter(IDataConverter): + pass From 75f430a98af16ffd95e48973f798bfb030eaf5a0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:00:23 +0300 Subject: [PATCH 687/973] import IDataConverter into the data_converter module --- rss_reader/data_converter/data_converter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 193a0b90..8549de61 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,4 +1,7 @@ +from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter + + class DataConverter(IDataConverter): pass From efa86e7b29f4d76a08fa097a018d6fbeb9b7d9a0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:01:28 +0300 Subject: [PATCH 688/973] override concat_data into the DataConverter --- rss_reader/data_converter/data_converter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 8549de61..84c59ace 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -4,4 +4,6 @@ class DataConverter(IDataConverter): - pass + + def concat_data(self, data, local_data) -> Optional[DataFrame]: + pass From a02471266deb6fd2acb7a0e1d619702cbcb95476 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:02:23 +0300 Subject: [PATCH 689/973] import Optional to the data_converter module --- rss_reader/data_converter/data_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 84c59ace..343fe529 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,5 +1,7 @@ +from typing import Optional + from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter From 5394b550ee4b19499961afafc1cc166485caabf2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:03:04 +0300 Subject: [PATCH 690/973] import DataFrame to the data_converter module --- rss_reader/data_converter/data_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 343fe529..071cd374 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,6 +1,7 @@ from typing import Optional +from pandas import DataFrame from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter From 864714cd2d466a9cba9681eb39c40de7d5551859 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:13:21 +0300 Subject: [PATCH 691/973] add typing to the arguments --- rss_reader/interfaces/idataconverter/idataconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 5f0944b6..e028e921 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -10,6 +10,6 @@ class IDataConverter(ABC): """Basic converter interface.""" @abstractmethod - def concat_data(self, data, local_data) -> Optional[DataFrame]: + def concat_data(self, data: List[dict], local_data: str) -> Optional[DataFrame]: """Get normalized data.""" pass From fcb0411d028e1d96c1c2e47107f4c0f09f29d7d3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:14:18 +0300 Subject: [PATCH 692/973] import Optional to the idataconverter module --- rss_reader/interfaces/idataconverter/idataconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index e028e921..df211834 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -2,7 +2,7 @@ from abc import abstractmethod, ABC -from typing import Optional +from typing import List, Optional from pandas import DataFrame From 8bf8fea40bdc2182b1024ad34b3ea7e69f898b05 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 07:19:54 +0300 Subject: [PATCH 693/973] change docstring in the idataconverter module --- .../interfaces/idataconverter/idataconverter.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index df211834..1edb38bf 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -10,6 +10,16 @@ class IDataConverter(ABC): """Basic converter interface.""" @abstractmethod - def concat_data(self, data: List[dict], local_data: str) -> Optional[DataFrame]: - """Get normalized data.""" + def concat_data(self, data: List[dict], + local_data: str) -> Optional[DataFrame]: + """Get normalized data. + + :param data: Data for concatenation. + :type data: List[dict] + :param local_data: The name of the file where the locally saved data + is stored. + :type local_data: str + :return: DataFrame with merged data. Duplicates are removed. + :rtype: Optional[DataFrame] + """ pass From 59f7bb4aaa123952d469f04a8e4fdb4cd531b376 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:34:28 +0300 Subject: [PATCH 694/973] normalize the received data --- rss_reader/data_converter/data_converter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 071cd374..a547ad52 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -9,4 +9,6 @@ class DataConverter(IDataConverter): def concat_data(self, data, local_data) -> Optional[DataFrame]: - pass + norm_data = json_normalize(data, record_path=['items'], + meta=['title_web_resource', 'link'], + record_prefix="item.") From a70b49849e5470781fac2938a0bd96423eca7cb4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:35:03 +0300 Subject: [PATCH 695/973] conver date --- rss_reader/data_converter/data_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index a547ad52..24245336 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -12,3 +12,5 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: norm_data = json_normalize(data, record_path=['items'], meta=['title_web_resource', 'link'], record_prefix="item.") + + norm_data = self._convert_date(norm_data, 'item.pubDate') From 4f6b8a651b7cc9ca4950deb730660239c48c3fcd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:35:42 +0300 Subject: [PATCH 696/973] concat data --- rss_reader/data_converter/data_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 24245336..e245b4dd 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -14,3 +14,5 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: record_prefix="item.") norm_data = self._convert_date(norm_data, 'item.pubDate') + data_concat = concat([local_data, norm_data], + ignore_index=True) From 948e99915c2488d747a93633021cf9fe034c0997 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:36:36 +0300 Subject: [PATCH 697/973] remove duplicates from generated data --- rss_reader/data_converter/data_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index e245b4dd..01098054 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -16,3 +16,5 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: norm_data = self._convert_date(norm_data, 'item.pubDate') data_concat = concat([local_data, norm_data], ignore_index=True) + data_concat.drop_duplicates(keep='first', inplace=True, + ignore_index=True) From 3fffc80d7f6e83b0918480cb5c3ec270c5085a02 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:37:30 +0300 Subject: [PATCH 698/973] import json_normalize in the data_converter module --- rss_reader/data_converter/data_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 01098054..2d1b95e5 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,7 +1,7 @@ from typing import Optional -from pandas import DataFrame +from pandas import DataFrame, json_normalize from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter From 26c71f3fe0656c8a53de5f12711c6d24e24c69ce Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:38:02 +0300 Subject: [PATCH 699/973] import concat in the data_converter module --- rss_reader/data_converter/data_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 2d1b95e5..f545ebf6 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,7 +1,7 @@ from typing import Optional -from pandas import DataFrame, json_normalize +from pandas import DataFrame, json_normalize, concat from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter From 465a87b1b4441af0cd8d916751caa15f3d25c4bd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:39:10 +0300 Subject: [PATCH 700/973] create _convert_date method --- rss_reader/data_converter/data_converter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index f545ebf6..9e2274c0 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -18,3 +18,8 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: ignore_index=True) data_concat.drop_duplicates(keep='first', inplace=True, ignore_index=True) + + def _convert_date(self, df: DataFrame, column_name: + str, format: str = '%Y-%m-%d', + utc: bool = True) -> DataFrame: + pass From b0cc80b97db2f1c0082acd017ce4fef39e2459d8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:40:32 +0300 Subject: [PATCH 701/973] convert the date to the desired format --- rss_reader/data_converter/data_converter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 9e2274c0..65e7e040 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -22,4 +22,6 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: def _convert_date(self, df: DataFrame, column_name: str, format: str = '%Y-%m-%d', utc: bool = True) -> DataFrame: - pass + df[column_name] = to_datetime(df.get(column_name), utc=utc) + df[column_name] = df.get(column_name).dt.date.apply( + lambda x: x.strftime(format)) From b8e92eb42ffb72519a52a46d058c756b9646ca65 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:41:38 +0300 Subject: [PATCH 702/973] return a DataFrame with the converted time format --- rss_reader/data_converter/data_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 65e7e040..70f3dc12 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -25,3 +25,4 @@ def _convert_date(self, df: DataFrame, column_name: df[column_name] = to_datetime(df.get(column_name), utc=utc) df[column_name] = df.get(column_name).dt.date.apply( lambda x: x.strftime(format)) + return df From a52726294dd8f73a1ff772263bb8477fc24a0938 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:42:29 +0300 Subject: [PATCH 703/973] import to_datetime into the data_converter --- rss_reader/data_converter/data_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 70f3dc12..c8ea8ae2 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,7 +1,7 @@ from typing import Optional -from pandas import DataFrame, json_normalize, concat +from pandas import DataFrame, json_normalize, concat, to_datetime from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter From fab2e4a54e7f93cdb99c7b6b2da06c2403f68625 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:43:54 +0300 Subject: [PATCH 704/973] add a dockstrint to the _convert_date --- rss_reader/data_converter/data_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index c8ea8ae2..90185f80 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -22,6 +22,8 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: def _convert_date(self, df: DataFrame, column_name: str, format: str = '%Y-%m-%d', utc: bool = True) -> DataFrame: + """Convert the date to the desired format.""" + df[column_name] = to_datetime(df.get(column_name), utc=utc) df[column_name] = df.get(column_name).dt.date.apply( lambda x: x.strftime(format)) From cdd0f30cc02d2ee9054092b534132abc0791cc75 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:54:47 +0300 Subject: [PATCH 705/973] add a dockstring to the concat_data method --- rss_reader/data_converter/data_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 90185f80..a21bda41 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -9,6 +9,8 @@ class DataConverter(IDataConverter): def concat_data(self, data, local_data) -> Optional[DataFrame]: + """Concat two DataFrames.""" + norm_data = json_normalize(data, record_path=['items'], meta=['title_web_resource', 'link'], record_prefix="item.") From 681866400e613b96be0184cb4a927b3121e84bf8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 19:58:30 +0300 Subject: [PATCH 706/973] import DataConverter into the saver module --- rss_reader/saver/saver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index c02e7f1b..254a6dd4 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -5,6 +5,7 @@ from rss_reader.interfaces.isaver.isaver import ISaveHandler from rss_reader.decorator.decorator import send_log_of_start_function +from rss_reader.data_converter.data_converter import DataConverter class AbstractSaveHandler(ISaveHandler): From 29f91723e49540c383a70ab67da5fc6f8101f20f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:05:44 +0300 Subject: [PATCH 707/973] create reader_files.py --- rss_reader/saver/reader_files.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/saver/reader_files.py diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py new file mode 100644 index 00000000..e69de29b From 659306db628e4affc86aa2fcabd2622df772ba2e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:06:19 +0300 Subject: [PATCH 708/973] create ReaderFiles class --- rss_reader/saver/reader_files.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index e69de29b..2128819e 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -0,0 +1,4 @@ + + +class ReaderFiles(IReadFile): + pass From 5e4c7c7e69e47f51f07eec56d09789764e584ea6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:06:55 +0300 Subject: [PATCH 709/973] create ireader_files.py --- rss_reader/interfaces/isaver/ireader_files.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/isaver/ireader_files.py diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py new file mode 100644 index 00000000..e69de29b From ecc692a01c89489f414cac88f009aaa46ea4778c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:07:50 +0300 Subject: [PATCH 710/973] create IReadFile class --- rss_reader/interfaces/isaver/ireader_files.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index e69de29b..2748f07f 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -0,0 +1,4 @@ + + +class IReadFile(ABC): + pass From 8ec16e27331f21907804ab0c56fa027b151f8a07 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:08:36 +0300 Subject: [PATCH 711/973] create read_csv_file method --- rss_reader/interfaces/isaver/ireader_files.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index 2748f07f..5cebead9 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -1,4 +1,9 @@ class IReadFile(ABC): - pass + @abstractmethod + def read_csv_file(self, file: str, + index_col_: str, + creater: ICreateFile, + encoding_: str = 'utf-8') -> Optional[DataFrame]: + pass From b8b6caaf5084fe36460ee81ce95b93cd667c9a25 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:14:47 +0300 Subject: [PATCH 712/973] add a dockstring to the read_csv_file in the IReadFile --- rss_reader/interfaces/isaver/ireader_files.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index 5cebead9..0be91b7f 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -6,4 +6,20 @@ def read_csv_file(self, file: str, index_col_: str, creater: ICreateFile, encoding_: str = 'utf-8') -> Optional[DataFrame]: + """Read csv file. + + :param file: File name. + :type file: str + :param index_col_: Column(s) to use as the row labels of the DataFrame, + either given as string name or column index. If a + sequence of int / str is given, a MultiIndex is used. + :type index_col_: str + :param creater: An object that implements the ability to create a file. + Used when the requested file does not exist. + :type creater: ICreateFile + :param encoding_: File encoding, defaults to 'utf-8' + :type encoding_: str, optional + :return: Return the read data as a DataFrame. + :rtype: Optional[DataFrame] + """ pass From 716c77981930a572c51b704e6e3c5aa4e5ab7d70 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:15:53 +0300 Subject: [PATCH 713/973] import abstractmethod to the ireader_files module --- rss_reader/interfaces/isaver/ireader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index 0be91b7f..9e080340 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -1,4 +1,6 @@ +from abc import abstractmethod + class IReadFile(ABC): @abstractmethod From f0c27eec4e51cb66c29088cdbb0a378388f5efbd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:16:14 +0300 Subject: [PATCH 714/973] import ABC to the ireader_files module --- rss_reader/interfaces/isaver/ireader_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index 9e080340..b48a655c 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -1,5 +1,5 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC class IReadFile(ABC): From 77ad894c038ffcf729cda60d32c266977e923241 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:16:51 +0300 Subject: [PATCH 715/973] import Optional to the ireader_files module --- rss_reader/interfaces/isaver/ireader_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index b48a655c..7978c5eb 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -1,4 +1,5 @@ +from typing import Optional from abc import abstractmethod, ABC From 5c9f5db1b06d06020a07c8c316e1524c7e5aeb2f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:17:22 +0300 Subject: [PATCH 716/973] import DataFrame to the ireader_files module --- rss_reader/interfaces/isaver/ireader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index 7978c5eb..b55e2d1f 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -1,4 +1,6 @@ +from pandas import DataFrame + from typing import Optional from abc import abstractmethod, ABC From d442150ead94a21e62f84f4ce9369975d25b331f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:19:47 +0300 Subject: [PATCH 717/973] create pathfile package --- rss_reader/pathfile/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/pathfile/__init__.py diff --git a/rss_reader/pathfile/__init__.py b/rss_reader/pathfile/__init__.py new file mode 100644 index 00000000..e69de29b From fb064bdd0fe9c095024517f37d1701f75d944d0b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:21:30 +0300 Subject: [PATCH 718/973] add a dockstring to the pathfile package --- rss_reader/pathfile/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/pathfile/__init__.py b/rss_reader/pathfile/__init__.py index e69de29b..53192651 100644 --- a/rss_reader/pathfile/__init__.py +++ b/rss_reader/pathfile/__init__.py @@ -0,0 +1 @@ +"""This package contains modules that work with file system paths.""" From f1c0ea0f5297d5316e15179bbefe14be1955ba55 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:22:14 +0300 Subject: [PATCH 719/973] create pathfile module --- rss_reader/pathfile/pathfile.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/pathfile/pathfile.py diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py new file mode 100644 index 00000000..e69de29b From d6b87bd8ffd50b03f6ee2292bc9b63d2b824fe81 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:26:47 +0300 Subject: [PATCH 720/973] create ipathfile package --- rss_reader/interfaces/ipathfile/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/ipathfile/__init__.py diff --git a/rss_reader/interfaces/ipathfile/__init__.py b/rss_reader/interfaces/ipathfile/__init__.py new file mode 100644 index 00000000..e69de29b From 42bd8345ba7b221432116a309dee8778151d8277 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:27:56 +0300 Subject: [PATCH 721/973] add a dockstring to the ipathfile package --- rss_reader/interfaces/ipathfile/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/ipathfile/__init__.py b/rss_reader/interfaces/ipathfile/__init__.py index e69de29b..eac1d972 100644 --- a/rss_reader/interfaces/ipathfile/__init__.py +++ b/rss_reader/interfaces/ipathfile/__init__.py @@ -0,0 +1 @@ +"""This package contains the pathfile interfaces.""" From ee8102871cae1ca43ad0fbeb0f0d52dfa5575103 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:28:48 +0300 Subject: [PATCH 722/973] create ipathfile module --- rss_reader/interfaces/ipathfile/ipathfile.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/ipathfile/ipathfile.py diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py new file mode 100644 index 00000000..e69de29b From c95f93bf9ce00fb807adb1661340539748ea91d7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:33:16 +0300 Subject: [PATCH 723/973] create ICreateFile class --- rss_reader/interfaces/ipathfile/ipathfile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index e69de29b..cc1c3e6b 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -0,0 +1,4 @@ + + +class ICreateFile(ABC): + pass From a88b3ca725c5ce6c16e5de2e985e68ec7ab9754d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:34:34 +0300 Subject: [PATCH 724/973] create an abstract create_file method --- rss_reader/interfaces/ipathfile/ipathfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index cc1c3e6b..1a7eb546 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -1,4 +1,7 @@ class ICreateFile(ABC): - pass + + @abstractmethod + def create_file(self, file: str) -> None: + pass From df851f41210cc44c19c58a0c8d8665176b02bce8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:35:47 +0300 Subject: [PATCH 725/973] import abstractmethod into the ipathfile module --- rss_reader/interfaces/ipathfile/ipathfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index 1a7eb546..5ef7ec6e 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -1,4 +1,6 @@ +from abc import abstractmethod + class ICreateFile(ABC): From 180037afd584ce41a429abc9a364a7cc7853efa1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:36:06 +0300 Subject: [PATCH 726/973] import ABC into the ipathfile module --- rss_reader/interfaces/ipathfile/ipathfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index 5ef7ec6e..ed49646d 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -1,5 +1,5 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC class ICreateFile(ABC): From 21de7e2863e2cfabdb419e16fb7eb4a1da4cdb48 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:37:35 +0300 Subject: [PATCH 727/973] add a docstring to the create_file abstract method --- rss_reader/interfaces/ipathfile/ipathfile.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index ed49646d..75429e5e 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -6,4 +6,9 @@ class ICreateFile(ABC): @abstractmethod def create_file(self, file: str) -> None: + """Create file. + + :param file: File name. + :type file: str + """ pass From 64fe87022ac6d5c0943e430fa6ce46d51f1418ae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:39:22 +0300 Subject: [PATCH 728/973] add a docstring to the ICreateFile --- rss_reader/interfaces/ipathfile/ipathfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index 75429e5e..0c1ffe18 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -3,6 +3,7 @@ class ICreateFile(ABC): + """A interface for creating files.""" @abstractmethod def create_file(self, file: str) -> None: From a4b9a66fcf956e698152599538631f831b539444 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:40:23 +0300 Subject: [PATCH 729/973] add a dockstring to the ipathfile module --- rss_reader/interfaces/ipathfile/ipathfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/ipathfile/ipathfile.py b/rss_reader/interfaces/ipathfile/ipathfile.py index 0c1ffe18..0d28cc9d 100644 --- a/rss_reader/interfaces/ipathfile/ipathfile.py +++ b/rss_reader/interfaces/ipathfile/ipathfile.py @@ -1,3 +1,5 @@ +"""This module contains a set of interfaces for the pathfile package.""" + from abc import abstractmethod, ABC From 5b5af9c057083fb7eeb7a11610e46e89dcefd492 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:48:01 +0300 Subject: [PATCH 730/973] create PathFile class --- rss_reader/pathfile/pathfile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index e69de29b..c4c7f3b3 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -0,0 +1,4 @@ + + +class PathFile(ICreateFile): + pass From 7835c0739d7716aedc6c13e1f06c81fb67ac2e72 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:48:59 +0300 Subject: [PATCH 731/973] create create_file method --- rss_reader/pathfile/pathfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index c4c7f3b3..3d3edfb6 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -1,4 +1,5 @@ class PathFile(ICreateFile): - pass + def create_file(self, file: str) -> None: + pass From 5af26634f3f6efa97db3b1601d55d3557f7f72ae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:52:48 +0300 Subject: [PATCH 732/973] implement the process of creating a file in function create_file --- rss_reader/pathfile/pathfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index 3d3edfb6..1f2af147 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -2,4 +2,5 @@ class PathFile(ICreateFile): def create_file(self, file: str) -> None: - pass + file = Path(file) + file.touch(exist_ok=True) From 882f2741c2f40329a54e59c7e6f0e9cc4155bfb4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:53:46 +0300 Subject: [PATCH 733/973] import ICreateFile into the pathfile module --- rss_reader/pathfile/pathfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index 1f2af147..669fc7a7 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -1,4 +1,6 @@ +from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile + class PathFile(ICreateFile): def create_file(self, file: str) -> None: From 5bd02473bedf7e35277cc21dcf15c5c2d9dbf56d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:54:08 +0300 Subject: [PATCH 734/973] import Path into the pathfile module --- rss_reader/pathfile/pathfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index 669fc7a7..48599f54 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -1,4 +1,6 @@ +from pathlib import Path + from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile From 84b65d820120f49b73cd6f34c404652b4c5eda55 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:55:41 +0300 Subject: [PATCH 735/973] add a dockstring to the create_file method --- rss_reader/pathfile/pathfile.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index 48599f54..b529aabf 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -6,5 +6,10 @@ class PathFile(ICreateFile): def create_file(self, file: str) -> None: + """Create file. + + :param file: File name. + :type file: str + """ file = Path(file) file.touch(exist_ok=True) From dabb2562a135eb818377ccfa932d8c091fee9201 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 20:57:46 +0300 Subject: [PATCH 736/973] add a dockstring to the PathFile class --- rss_reader/pathfile/pathfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index b529aabf..c815b02d 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -5,6 +5,8 @@ class PathFile(ICreateFile): + """Works with the file system of the operating system.""" + def create_file(self, file: str) -> None: """Create file. From 57bc993e63ff354ec25da0bb4cff924e0802a931 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:34:33 +0300 Subject: [PATCH 737/973] add a docstring to the pathfile module --- rss_reader/pathfile/pathfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index c815b02d..861141c7 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -1,3 +1,5 @@ +"""This module contains objects that work with the file system of the OS.""" + from pathlib import Path From f7a846733b5ff3c6b77106d7a2418bfd673ace18 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:36:01 +0300 Subject: [PATCH 738/973] import ICreateFile into the ireader_files module --- rss_reader/interfaces/isaver/ireader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/isaver/ireader_files.py b/rss_reader/interfaces/isaver/ireader_files.py index b55e2d1f..60006f08 100644 --- a/rss_reader/interfaces/isaver/ireader_files.py +++ b/rss_reader/interfaces/isaver/ireader_files.py @@ -4,6 +4,8 @@ from typing import Optional from abc import abstractmethod, ABC +from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile + class IReadFile(ABC): @abstractmethod From b463263e31c53baa434e2c074a2e1f17ab5cc95b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:45:33 +0300 Subject: [PATCH 739/973] import IReadFile into the read_files module --- rss_reader/saver/reader_files.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 2128819e..01cdf973 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,4 +1,7 @@ +from rss_reader.interfaces.isaver.ireader_files import IReadFile + + class ReaderFiles(IReadFile): pass From 0ec6b2b61c7a99bc2b9db1380497c8ea18ae3f15 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:46:25 +0300 Subject: [PATCH 740/973] create read_csv_file method in the ReaderFiles class --- rss_reader/saver/reader_files.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 01cdf973..8e1fc9f4 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -4,4 +4,8 @@ class ReaderFiles(IReadFile): - pass + def read_csv_file(self, file: str, + index_col_: str, + creater: ICreateFile, + encoding_: str = 'utf-8') -> Optional[DataFrame]: + pass From 2b8db687bf8df2085e9e1bafe1e8d3d9ad5dbd5b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:47:06 +0300 Subject: [PATCH 741/973] declare local_storage variable --- rss_reader/saver/reader_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 8e1fc9f4..1683da35 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -8,4 +8,4 @@ def read_csv_file(self, file: str, index_col_: str, creater: ICreateFile, encoding_: str = 'utf-8') -> Optional[DataFrame]: - pass + local_storage = None From 519781707731f17386d9fe1086ae1b963fb5807b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:47:44 +0300 Subject: [PATCH 742/973] read csv file --- rss_reader/saver/reader_files.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 1683da35..5754eb66 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -9,3 +9,6 @@ def read_csv_file(self, file: str, creater: ICreateFile, encoding_: str = 'utf-8') -> Optional[DataFrame]: local_storage = None + local_storage = read_csv(file, + index_col=index_col_, + encoding=encoding_) From 1dca61d7a288d1978faad70d40185a97e1bb58d8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:49:32 +0300 Subject: [PATCH 743/973] catch the EmptyDataError error --- rss_reader/saver/reader_files.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 5754eb66..890ba689 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -9,6 +9,10 @@ def read_csv_file(self, file: str, creater: ICreateFile, encoding_: str = 'utf-8') -> Optional[DataFrame]: local_storage = None - local_storage = read_csv(file, - index_col=index_col_, - encoding=encoding_) + + try: + local_storage = read_csv(file, + index_col=index_col_, + encoding=encoding_) + except EmptyDataError as e: + pass From b2f5ad3fdb08070d6517b2c020b290ec4bac919f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:49:54 +0300 Subject: [PATCH 744/973] catch the FileNotFoundError error --- rss_reader/saver/reader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 890ba689..137b3e2c 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -16,3 +16,5 @@ def read_csv_file(self, file: str, encoding=encoding_) except EmptyDataError as e: pass + except FileNotFoundError as e: + pass From b222a8fe1aeb10abc7d0cf2870423d878039f6f1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 21:51:54 +0300 Subject: [PATCH 745/973] return read data --- rss_reader/saver/reader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 137b3e2c..ed73a92e 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -18,3 +18,5 @@ def read_csv_file(self, file: str, pass except FileNotFoundError as e: pass + + return local_storage From 3acba23312cbf92a178111f1e5a98d2798d5d107 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:03:46 +0300 Subject: [PATCH 746/973] import ICreateFile into the reader_files module --- rss_reader/saver/reader_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index ed73a92e..d1e13196 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,6 +1,7 @@ from rss_reader.interfaces.isaver.ireader_files import IReadFile +from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile class ReaderFiles(IReadFile): From 273730e2055a39f5048649ba2db44a0568b5e2b2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:04:18 +0300 Subject: [PATCH 747/973] import Optional into the reader_files module --- rss_reader/saver/reader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index d1e13196..ec7657e6 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,5 +1,7 @@ +from typing import Optional + from rss_reader.interfaces.isaver.ireader_files import IReadFile from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile From b84702e466ab95bfb9cc8be44b844e816e0eb0dc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:04:41 +0300 Subject: [PATCH 748/973] import DataFrame into the reader_files module --- rss_reader/saver/reader_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index ec7657e6..c2036428 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,5 +1,5 @@ - +from pandas import DataFrame from typing import Optional from rss_reader.interfaces.isaver.ireader_files import IReadFile From 779cf73a35c2ef90fc0765c1e121f5da7beb893c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:04:58 +0300 Subject: [PATCH 749/973] import read_csv into the reader_files module --- rss_reader/saver/reader_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index c2036428..0620cd9d 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,5 +1,5 @@ -from pandas import DataFrame +from pandas import DataFrame, read_csv from typing import Optional from rss_reader.interfaces.isaver.ireader_files import IReadFile From 882bf2892810d5fb24f12dd699bb3f4a0b7b0027 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:05:32 +0300 Subject: [PATCH 750/973] import EmptyDataError into the reader_files module --- rss_reader/saver/reader_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 0620cd9d..498e0bf2 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,5 +1,6 @@ from pandas import DataFrame, read_csv +from pandas.errors import EmptyDataError from typing import Optional from rss_reader.interfaces.isaver.ireader_files import IReadFile From c1cb01f838ce9a3a7a2d1a5adbe6d700ebee869a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:06:17 +0300 Subject: [PATCH 751/973] import Logger into the reader_files module --- rss_reader/saver/reader_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 498e0bf2..77627709 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -5,6 +5,7 @@ from rss_reader.interfaces.isaver.ireader_files import IReadFile from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile +from rss_reader.logger.logger import Logger class ReaderFiles(IReadFile): From 359c8bf4ea5ef1b389d20eef112b938bc5b3939c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:08:23 +0300 Subject: [PATCH 752/973] get program logger --- rss_reader/saver/reader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 77627709..0b59e4cd 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -7,6 +7,8 @@ from rss_reader.interfaces.ipathfile.ipathfile import ICreateFile from rss_reader.logger.logger import Logger +log = Logger.get_logger(__name__) + class ReaderFiles(IReadFile): def read_csv_file(self, file: str, From 694efc1fb83dff4ce1a83c708d731b17135e1228 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:10:03 +0300 Subject: [PATCH 753/973] log file not found --- rss_reader/saver/reader_files.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 0b59e4cd..37dca514 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -22,8 +22,9 @@ def read_csv_file(self, file: str, index_col=index_col_, encoding=encoding_) except EmptyDataError as e: - pass + log.error(f'{file} is empty') except FileNotFoundError as e: - pass + log.exception(f'No such file or directory: {file}') + creater.create_file(file) return local_storage From 228abaaa3a5ec8728233695ab8c98f841b96fce3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:11:56 +0300 Subject: [PATCH 754/973] add a docstring to the read_csv_file from the ReaderFiles --- rss_reader/saver/reader_files.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 37dca514..948fea56 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -15,6 +15,24 @@ def read_csv_file(self, file: str, index_col_: str, creater: ICreateFile, encoding_: str = 'utf-8') -> Optional[DataFrame]: + """Read csv file. + + If the file does not exist, it will be created + + :param file: File name. + :type file: str + :param index_col_: Column(s) to use as the row labels of the DataFrame, + either given as string name or column index. If a + sequence of int / str is given, a MultiIndex is used. + :type index_col_: str + :param creater: An object that implements the ability to create a file. + Used when the requested file does not exist. + :type creater: ICreateFile + :param encoding_: File encoding, defaults to 'utf-8' + :type encoding_: str, optional + :return: Return the read data as a DataFrame. + :rtype: Optional[DataFrame] + """ local_storage = None try: From 5f4c45407ba33281134f92aaf50d0fc0accf1f67 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:13:29 +0300 Subject: [PATCH 755/973] add a dockstring to the ReaderFiles class --- rss_reader/saver/reader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 948fea56..9f6dc2d3 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -11,6 +11,8 @@ class ReaderFiles(IReadFile): + """A class to represent a file reader.""" + def read_csv_file(self, file: str, index_col_: str, creater: ICreateFile, From 24dc909ef7a71fe231b25872b4aeb3815311c74b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:15:19 +0300 Subject: [PATCH 756/973] add a dockstring to the reader_files module --- rss_reader/saver/reader_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 9f6dc2d3..42b57b20 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -1,3 +1,5 @@ +"""This module contains objects that read files.""" + from pandas import DataFrame, read_csv from pandas.errors import EmptyDataError From dafcaf7aa0c01d8b5d19a680a8fafcd9b862a1f4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:19:30 +0300 Subject: [PATCH 757/973] import ReaderFiles to the saver module --- rss_reader/saver/saver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index 254a6dd4..eedd2b4e 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -6,6 +6,7 @@ from rss_reader.interfaces.isaver.isaver import ISaveHandler from rss_reader.decorator.decorator import send_log_of_start_function from rss_reader.data_converter.data_converter import DataConverter +from rss_reader.saver.reader_files import ReaderFiles class AbstractSaveHandler(ISaveHandler): From d84353b56f7b0dcbfaba51f710934605ca3182ac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:20:07 +0300 Subject: [PATCH 758/973] import ReaderFiles to the saver module --- rss_reader/saver/saver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index eedd2b4e..fcfebd39 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -7,6 +7,7 @@ from rss_reader.decorator.decorator import send_log_of_start_function from rss_reader.data_converter.data_converter import DataConverter from rss_reader.saver.reader_files import ReaderFiles +from rss_reader.pathfile.pathfile import PathFile class AbstractSaveHandler(ISaveHandler): From dd0c5ebf0b48d8e1be542675b3fc6ace6b785a03 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:21:29 +0300 Subject: [PATCH 759/973] replace CreaterFiles with PathFile --- rss_reader/saver/saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index fcfebd39..a0537b41 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -41,7 +41,7 @@ def save(self, data: List[dict], file: str) -> None: class LocalSaveHandler(AbstractSaveHandler): def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: - local_data = ReaderFiles().read_csv_file(file, 'index', CreaterFiles()) + local_data = ReaderFiles().read_csv_file(file, 'index', PathFile()) try: norm_data = DataConverter().concat_data(data, local_data) From 73befd7026850785e8696d4db305bcada48fecca Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:59:06 +0300 Subject: [PATCH 760/973] create idateconverter package --- rss_reader/interfaces/idateconverter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/idateconverter/__init__.py diff --git a/rss_reader/interfaces/idateconverter/__init__.py b/rss_reader/interfaces/idateconverter/__init__.py new file mode 100644 index 00000000..e69de29b From e12d24b5f9974617ec03819cf8242832a194f31d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 22:59:50 +0300 Subject: [PATCH 761/973] add a docstring to the idateconverter package --- rss_reader/interfaces/idateconverter/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/interfaces/idateconverter/__init__.py b/rss_reader/interfaces/idateconverter/__init__.py index e69de29b..eff3a80b 100644 --- a/rss_reader/interfaces/idateconverter/__init__.py +++ b/rss_reader/interfaces/idateconverter/__init__.py @@ -0,0 +1 @@ +"""This package contains the dateconverter interfaces.""" From e1e9e86b0c95c3c42aed0ccbd6a3466bf5466fdc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:00:23 +0300 Subject: [PATCH 762/973] create iadteconverter module --- rss_reader/interfaces/idateconverter/idateconverter.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/idateconverter/idateconverter.py diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py new file mode 100644 index 00000000..e69de29b From 87edbbdc8036d734d0115eb486760551db6e11da Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:02:46 +0300 Subject: [PATCH 763/973] create IDateConverter class --- rss_reader/interfaces/idateconverter/idateconverter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index e69de29b..d0462c9b 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -0,0 +1,3 @@ + +class IDateConverter(ABC): + pass From 33707db80514c185caeaf058f6464774cf82c1bc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:06:47 +0300 Subject: [PATCH 764/973] create date_conver method --- rss_reader/interfaces/idateconverter/idateconverter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index d0462c9b..57a31102 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -1,3 +1,6 @@ class IDateConverter(ABC): - pass + @staticmethod + @abstractmethod + def date_convert(date: str, format: str) -> str: + pass From 525158540d46cc12793474a24ed9f5260474fdea Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:09:54 +0300 Subject: [PATCH 765/973] add a docstring to the date_convert method --- rss_reader/interfaces/idateconverter/idateconverter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index 57a31102..6724f2aa 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -3,4 +3,14 @@ class IDateConverter(ABC): @staticmethod @abstractmethod def date_convert(date: str, format: str) -> str: + """Return the textual representation of the substring. + + :param date: Date in original state. + :type date: str + :param format: Substring selection format. + Example '%Y%m%d'. + :type format: str + :return: Date in the given format. + :rtype: str + """ pass From d14875d96e2360e5e35e52c2f408cf27fd050bb2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:11:20 +0300 Subject: [PATCH 766/973] add a docstring to the adateconverter module --- rss_reader/interfaces/idateconverter/idateconverter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index 6724f2aa..882ba166 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -1,5 +1,9 @@ +"""This module contains classes that work with dates.""" + class IDateConverter(ABC): + """Converts date.""" + @staticmethod @abstractmethod def date_convert(date: str, format: str) -> str: From a10f552c5d081893c5d7681fcb3d9333e7c70b52 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:12:06 +0300 Subject: [PATCH 767/973] import ABC into the idateconverter module --- rss_reader/interfaces/idateconverter/idateconverter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index 882ba166..e116a577 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -1,5 +1,7 @@ """This module contains classes that work with dates.""" +from abc import ABC + class IDateConverter(ABC): """Converts date.""" From 8fea6406868f8f21c09aa1c5abb0f74695ad24f4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:12:26 +0300 Subject: [PATCH 768/973] import abstractmethod into the idateconverter module --- rss_reader/interfaces/idateconverter/idateconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index e116a577..38d822d5 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -1,6 +1,6 @@ """This module contains classes that work with dates.""" -from abc import ABC +from abc import ABC, abstractmethod class IDateConverter(ABC): From 47650bf05ac86623884bbfa385663f87bd3e9f77 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:18:29 +0300 Subject: [PATCH 769/973] create date_converter package --- rss_reader/date_converter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/date_converter/__init__.py diff --git a/rss_reader/date_converter/__init__.py b/rss_reader/date_converter/__init__.py new file mode 100644 index 00000000..e69de29b From 741466c4ad0366d63ebb27e0c0a605f722574bb8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:19:17 +0300 Subject: [PATCH 770/973] create date_converter module --- rss_reader/date_converter/date_converter.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/date_converter/date_converter.py diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py new file mode 100644 index 00000000..e69de29b From 479f1cc866c6f543f90d3881d0e8585a5ead8e90 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:19:59 +0300 Subject: [PATCH 771/973] create DateConverter class --- rss_reader/date_converter/date_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index e69de29b..3e80e44a 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -0,0 +1,2 @@ +class DateConverter(IDateConverter): + pass From 30f91807e475249c4f9481168322ec019e51d6bb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:21:02 +0300 Subject: [PATCH 772/973] import IDateConverter into the date_converter module --- rss_reader/date_converter/date_converter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index 3e80e44a..74d24e9b 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -1,2 +1,6 @@ + +from rss_reader.interfaces.idateconverter.idateconverter import IDateConverter + + class DateConverter(IDateConverter): pass From a82569d173736c736093ffc82a5a2b09d1c3ad45 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:24:14 +0300 Subject: [PATCH 773/973] create date_convert method --- rss_reader/date_converter/date_converter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index 74d24e9b..ed739f7b 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -3,4 +3,6 @@ class DateConverter(IDateConverter): - pass + @staticmethod + def date_convert(date: str, format: str = '%Y%m%d') -> str: + pass From 98982baf1accf4591849950cb25953c901741c6a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:25:02 +0300 Subject: [PATCH 774/973] return the date as a string --- rss_reader/date_converter/date_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index ed739f7b..2b8fd052 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -5,4 +5,4 @@ class DateConverter(IDateConverter): @staticmethod def date_convert(date: str, format: str = '%Y%m%d') -> str: - pass + return datetime.strptime(date, format).date().__str__() From e606ad7d2c898f00943236ccbf77ee56a60da1d8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:25:35 +0300 Subject: [PATCH 775/973] import datetime --- rss_reader/date_converter/date_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index 2b8fd052..d9429330 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -1,4 +1,6 @@ +from datetime import datetime + from rss_reader.interfaces.idateconverter.idateconverter import IDateConverter From ee7aa3750b4ea50baa764be1ce675f8ab27965c5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:28:02 +0300 Subject: [PATCH 776/973] add a docstring to the date_convert --- rss_reader/date_converter/date_converter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index d9429330..66cc5445 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -7,4 +7,14 @@ class DateConverter(IDateConverter): @staticmethod def date_convert(date: str, format: str = '%Y%m%d') -> str: + """Return the date part. + + :param date: Date in original state. + :type date: str + :param format: Substring selection format. + Example '%Y%m%d'. + :type format: str + :return: Date in the given format. + :rtype: str + """ return datetime.strptime(date, format).date().__str__() From bcdfb72a92a48281ae6a779bd85849a82cf89cf5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:29:05 +0300 Subject: [PATCH 777/973] fix. Replace docstring header --- rss_reader/interfaces/idateconverter/idateconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/idateconverter/idateconverter.py b/rss_reader/interfaces/idateconverter/idateconverter.py index 38d822d5..f67407d2 100644 --- a/rss_reader/interfaces/idateconverter/idateconverter.py +++ b/rss_reader/interfaces/idateconverter/idateconverter.py @@ -9,7 +9,7 @@ class IDateConverter(ABC): @staticmethod @abstractmethod def date_convert(date: str, format: str) -> str: - """Return the textual representation of the substring. + """Return the date part. :param date: Date in original state. :type date: str From e94692ef84d610063216af18828936a1fb4087e3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:30:04 +0300 Subject: [PATCH 778/973] add a docstring to the DateConverter class --- rss_reader/date_converter/date_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index 66cc5445..44f430f0 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -5,6 +5,8 @@ class DateConverter(IDateConverter): + """Converts date.""" + @staticmethod def date_convert(date: str, format: str = '%Y%m%d') -> str: """Return the date part. From 654f10da10331b0951cc74e13e8546d20ddde923 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:30:43 +0300 Subject: [PATCH 779/973] add a docstring to the date_converter module --- rss_reader/date_converter/date_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/date_converter/date_converter.py b/rss_reader/date_converter/date_converter.py index 44f430f0..b948dc47 100644 --- a/rss_reader/date_converter/date_converter.py +++ b/rss_reader/date_converter/date_converter.py @@ -1,3 +1,5 @@ +"""This module contains classes that work with dates""" + from datetime import datetime From d2ee4e0885a212f456215d283e105fccaab62730 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:33:46 +0300 Subject: [PATCH 780/973] create decorators module --- rss_reader/loader/decorators.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/loader/decorators.py diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py new file mode 100644 index 00000000..e69de29b From 63c824cdbf3854c3efc022dcec17ed0747f16a62 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:34:50 +0300 Subject: [PATCH 781/973] create IComponent class --- rss_reader/loader/decorators.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index e69de29b..b2058a0b 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -0,0 +1,7 @@ + + +class IComponent(ABC): + + @abstractmethod + def operation(self, data: DataFrame) -> DataFrame: + pass \ No newline at end of file From ed4641a0ea0b8033950b944ae161ee728f6921c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:35:44 +0300 Subject: [PATCH 782/973] add a dockstring to the IComponent class --- rss_reader/loader/decorators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index b2058a0b..f5145edb 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -1,7 +1,8 @@ class IComponent(ABC): + """Basic Component Interface""" @abstractmethod def operation(self, data: DataFrame) -> DataFrame: - pass \ No newline at end of file + pass From e1b06299c7d67271b8516a2457c1f7c771bf2b57 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:37:14 +0300 Subject: [PATCH 783/973] import ABC, abstractmethod to the decorators module --- rss_reader/loader/decorators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index f5145edb..ce12945b 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -1,3 +1,4 @@ +from abc import ABC, abstractmethod class IComponent(ABC): From 3277eee6834281b5ab8bb17775a69a72b14ad339 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:37:38 +0300 Subject: [PATCH 784/973] import DateFrame to the decorators module --- rss_reader/loader/decorators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index ce12945b..992caa49 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -1,5 +1,7 @@ from abc import ABC, abstractmethod +from pandas import DataFrame + class IComponent(ABC): """Basic Component Interface""" From 105932dfa99b29ec029ad9b5486099d8a17e4eb2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:38:11 +0300 Subject: [PATCH 785/973] create BaseComponent class --- rss_reader/loader/decorators.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 992caa49..6e58c1c1 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -9,3 +9,8 @@ class IComponent(ABC): @abstractmethod def operation(self, data: DataFrame) -> DataFrame: pass + + +class BaseComponent(IComponent): + def operation(self, data) -> str: + return data From 59993650dedfeb3aa09e35f862d0460cd0041ee2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:39:02 +0300 Subject: [PATCH 786/973] add a docstring to the BaseComponent --- rss_reader/loader/decorators.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 6e58c1c1..036c22df 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -12,5 +12,10 @@ def operation(self, data: DataFrame) -> DataFrame: class BaseComponent(IComponent): + """A base concrete component. + + It is a stub. + """ + def operation(self, data) -> str: return data From 1f02daf2ce4b0d8f8cd8f0f06dec63b866d06eaa Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:39:45 +0300 Subject: [PATCH 787/973] create Decorator class --- rss_reader/loader/decorators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 036c22df..955ba066 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -19,3 +19,7 @@ class BaseComponent(IComponent): def operation(self, data) -> str: return data + + +class Decorator(IComponent): + pass From b382b29940187da413a25236fdd0d1233842762c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:40:40 +0300 Subject: [PATCH 788/973] initialize the Decorator class --- rss_reader/loader/decorators.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 955ba066..fbafb453 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -22,4 +22,8 @@ def operation(self, data) -> str: class Decorator(IComponent): - pass + + _component: IComponent = None + + def __init__(self, component: IComponent) -> None: + self._component = component From dc663f083353dc3812f21e9d5186ee5c72159fcd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:41:18 +0300 Subject: [PATCH 789/973] create component property --- rss_reader/loader/decorators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index fbafb453..57c759db 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -27,3 +27,7 @@ class Decorator(IComponent): def __init__(self, component: IComponent) -> None: self._component = component + + @property + def component(self) -> IComponent: + return self._component From dc9afe7922e518646d42ba63b1f3fde33eae8a9f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:42:05 +0300 Subject: [PATCH 790/973] override operation method in the Decorator class --- rss_reader/loader/decorators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 57c759db..72d2a6a1 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -31,3 +31,6 @@ def __init__(self, component: IComponent) -> None: @property def component(self) -> IComponent: return self._component + + def operation(self, data) -> DataFrame: + return self._component.operation(data) From 04df1e3adfe10caaaecf83b1ffd9488a10dbc851 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:42:56 +0300 Subject: [PATCH 791/973] add a dockstring o the Decorator class --- rss_reader/loader/decorators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 72d2a6a1..6870b1ba 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -22,6 +22,7 @@ def operation(self, data) -> str: class Decorator(IComponent): + """Decorator base class.""" _component: IComponent = None From b938a4248e6088f2d02668c2536f79b791793034 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:43:25 +0300 Subject: [PATCH 792/973] create LimitRecords class --- rss_reader/loader/decorators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 6870b1ba..98a32476 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -35,3 +35,6 @@ def component(self) -> IComponent: def operation(self, data) -> DataFrame: return self._component.operation(data) + +class LimitRecords(Decorator): + pass \ No newline at end of file From 9020bf2390a2baa75cd4911381dd425069ebf40f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:46:37 +0300 Subject: [PATCH 793/973] initialize the LimitRecords class --- rss_reader/loader/decorators.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 98a32476..76bbb1cf 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -36,5 +36,8 @@ def component(self) -> IComponent: def operation(self, data) -> DataFrame: return self._component.operation(data) + class LimitRecords(Decorator): - pass \ No newline at end of file + def __init__(self, limit: int, component: IComponent) -> None: + self._limit = limit + super().__init__(component) From cda2f6dcef2c1e4dce90b85bfec0545a5b32a66f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:48:12 +0300 Subject: [PATCH 794/973] add a dockstring to the __init__ in the LimitRecords --- rss_reader/loader/decorators.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 76bbb1cf..10410621 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -39,5 +39,12 @@ def operation(self, data) -> DataFrame: class LimitRecords(Decorator): def __init__(self, limit: int, component: IComponent) -> None: + """Initializer. + + :param limit: How many records to return. + :type limit: int + :param component: object of type IComponent. + :type component: IComponent + """ self._limit = limit super().__init__(component) From 94107a09648b41d99b63b7cdba0b9e84d6ac2778 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:49:48 +0300 Subject: [PATCH 795/973] create operation method in the LimitRecords --- rss_reader/loader/decorators.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 10410621..fa30c9a4 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -48,3 +48,14 @@ def __init__(self, limit: int, component: IComponent) -> None: """ self._limit = limit super().__init__(component) + + def operation(self, data: DataFrame) -> DataFrame: + """Return the required number of data records. + + :param data: Sample data. + :type data: DataFrame + :return: Data sampling. + :rtype: DataFrame + """ + result = self.component.operation(data) + return result.head(self._limit) From f41886f6cf73abb141ebb5750c20cfb5f1bac0dc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:50:31 +0300 Subject: [PATCH 796/973] add a docstring to the LimitRecords class --- rss_reader/loader/decorators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index fa30c9a4..c9486aee 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -38,6 +38,8 @@ def operation(self, data) -> DataFrame: class LimitRecords(Decorator): + """A decorator that produces a certain number of entries.""" + def __init__(self, limit: int, component: IComponent) -> None: """Initializer. From 50a46a7d86faca8a1262724fb081f06041f1b615 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:51:04 +0300 Subject: [PATCH 797/973] create SortByEqual class --- rss_reader/loader/decorators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index c9486aee..70993b08 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -61,3 +61,7 @@ def operation(self, data: DataFrame) -> DataFrame: """ result = self.component.operation(data) return result.head(self._limit) + + +class SortByEqual(Decorator): + pass From dd7974ab80288209625c69d9e5762515455ef293 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:53:03 +0300 Subject: [PATCH 798/973] initialize the __init__ function in the SortByEqual class --- rss_reader/loader/decorators.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 70993b08..c77653ed 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -64,4 +64,17 @@ def operation(self, data: DataFrame) -> DataFrame: class SortByEqual(Decorator): - pass + def __init__(self, search_column: str, criterion: str, + component: IComponent) -> None: + """Initializer. + + :param search_column: the name of the column to select from. + :type search_column: str + :param criterion: comparison criterion. + :type criterion: str + :param component: object of type IComponent. + :type component: IComponent + """ + self._search_column = search_column + self._criterion = criterion + super().__init__(component) From ae5ac020828885ae8ef2737c267184c69e7931a2 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:53:53 +0300 Subject: [PATCH 799/973] create operation method in the SortByEqual class --- rss_reader/loader/decorators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index c77653ed..867a7a78 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -78,3 +78,7 @@ def __init__(self, search_column: str, criterion: str, self._search_column = search_column self._criterion = criterion super().__init__(component) + + def operation(self, data: DataFrame) -> DataFrame: + result = self.component.operation(data) + return result[result[self._search_column] == self._criterion] From 3833b6ff94ce5f9383733c3b9d6b846f86b54331 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Mon, 27 Jun 2022 23:55:01 +0300 Subject: [PATCH 800/973] add a dockstring to the operation method --- rss_reader/loader/decorators.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 867a7a78..311e3d60 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -80,5 +80,12 @@ def __init__(self, search_column: str, criterion: str, super().__init__(component) def operation(self, data: DataFrame) -> DataFrame: + """Return records that match the criteria. + + :param data: sample data. + :type data: DataFrame + :return: data sampling. + :rtype: DataFrame + """ result = self.component.operation(data) return result[result[self._search_column] == self._criterion] From c870683c5c8e3c4379a0b5aa365241e9e4f9b707 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:01:48 +0300 Subject: [PATCH 801/973] create reader module in th loader package --- rss_reader/loader/reader.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/loader/reader.py diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py new file mode 100644 index 00000000..e69de29b From 6a55dea12ac89698491cadadedccf2ca1a9eea71 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:02:32 +0300 Subject: [PATCH 802/973] create ReaderCSVFile class --- rss_reader/loader/reader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py index e69de29b..c4fa8521 100644 --- a/rss_reader/loader/reader.py +++ b/rss_reader/loader/reader.py @@ -0,0 +1,3 @@ + +class ReaderCSVFile(IReadFile): + pass From ebf62871cdc225d8be37703ab24de949abc617dc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:03:35 +0300 Subject: [PATCH 803/973] create read method --- rss_reader/loader/reader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py index c4fa8521..e8dfa0de 100644 --- a/rss_reader/loader/reader.py +++ b/rss_reader/loader/reader.py @@ -1,3 +1,6 @@ class ReaderCSVFile(IReadFile): - pass + @staticmethod + def read(file: str, + index_col: str = 'index', encoding='utf-8') -> DataFrame: + pass From fa545eae74d643e418995c50d4c9fdcb2c921e7b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:05:20 +0300 Subject: [PATCH 804/973] implement function read --- rss_reader/loader/reader.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py index e8dfa0de..43760ecd 100644 --- a/rss_reader/loader/reader.py +++ b/rss_reader/loader/reader.py @@ -3,4 +3,11 @@ class ReaderCSVFile(IReadFile): @staticmethod def read(file: str, index_col: str = 'index', encoding='utf-8') -> DataFrame: - pass + try: + raw_data = read_csv(file, index_col=index_col, encoding=encoding) + except FileNotFoundError as e: + raise DataFileNotFoundError(file) from e + except EmptyDataError as e: + raise DataFileEmptyError(file) from e + + return raw_data From 089dd3dfea9d984cc0b58508b7f3feb343dcf7ae Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:07:35 +0300 Subject: [PATCH 805/973] load modules --- rss_reader/loader/reader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py index 43760ecd..b93488f3 100644 --- a/rss_reader/loader/reader.py +++ b/rss_reader/loader/reader.py @@ -1,4 +1,8 @@ +from pandas import DataFrame, read_csv +from pandas.errors import EmptyDataError + + class ReaderCSVFile(IReadFile): @staticmethod def read(file: str, From c3c290514d11c2b1ea8ad3b9c579eaa70eb307fe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:08:29 +0300 Subject: [PATCH 806/973] create exceptions module in load package --- rss_reader/loader/exceptions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/loader/exceptions.py diff --git a/rss_reader/loader/exceptions.py b/rss_reader/loader/exceptions.py new file mode 100644 index 00000000..e69de29b From 6bce3259abb781e041928532a2c8903700165e09 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:10:34 +0300 Subject: [PATCH 807/973] create EmptyURLError class --- rss_reader/loader/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/exceptions.py b/rss_reader/loader/exceptions.py index e69de29b..ce241558 100644 --- a/rss_reader/loader/exceptions.py +++ b/rss_reader/loader/exceptions.py @@ -0,0 +1,4 @@ + + +class EmptyURLError(Exception): + """Occurs when the url is empty.""" From 4d8b5b49329cc77b8a50fc2ddf12e49d81a09314 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:11:00 +0300 Subject: [PATCH 808/973] create DataEmptyError class --- rss_reader/loader/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/exceptions.py b/rss_reader/loader/exceptions.py index ce241558..357c8102 100644 --- a/rss_reader/loader/exceptions.py +++ b/rss_reader/loader/exceptions.py @@ -2,3 +2,7 @@ class EmptyURLError(Exception): """Occurs when the url is empty.""" + + +class DataEmptyError(Exception): + """Occurs when there is no data.""" From 54471fa1e0cc55782a5c2b874be362e7c34f6d15 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:11:30 +0300 Subject: [PATCH 809/973] create DataFileEmptyError class --- rss_reader/loader/exceptions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/loader/exceptions.py b/rss_reader/loader/exceptions.py index 357c8102..067f7c8a 100644 --- a/rss_reader/loader/exceptions.py +++ b/rss_reader/loader/exceptions.py @@ -6,3 +6,17 @@ class EmptyURLError(Exception): class DataEmptyError(Exception): """Occurs when there is no data.""" + + +class DataFileEmptyError(EmptyDataError): + """Occurs when there is no data in the uploaded file.""" + + def __init__(self, file, *args, **kwargs) -> None: + self.file = file + super().__init__(*args, **kwargs) + + def __str__(self) -> str: + a = f'No columns to parse from file ({self.file}). '\ + f'Delete the file and run the program in the mode of reading '\ + f'news from the Internet.' + return a From 2b7d847c5255343fecfa6781f0f31d82151051c4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:11:50 +0300 Subject: [PATCH 810/973] create DataFileNotFoundError class --- rss_reader/loader/exceptions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/loader/exceptions.py b/rss_reader/loader/exceptions.py index 067f7c8a..a131fb5c 100644 --- a/rss_reader/loader/exceptions.py +++ b/rss_reader/loader/exceptions.py @@ -20,3 +20,17 @@ def __str__(self) -> str: f'Delete the file and run the program in the mode of reading '\ f'news from the Internet.' return a + + +class DataFileNotFoundError(EmptyDataError): + """Occurs when the file is not found.""" + + def __init__(self, file, *args, **kwargs) -> None: + self.file = file + super().__init__(*args, **kwargs) + + def __str__(self) -> str: + a = f'File ({self.file}) does not exist. '\ + f'Run the program in the mode of reading '\ + f'news from the Internet.' + return a From b512f5b9d2770f49172c1b8f2be9122ddd5e8c30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:12:18 +0300 Subject: [PATCH 811/973] import EmptyDataError --- rss_reader/loader/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/exceptions.py b/rss_reader/loader/exceptions.py index a131fb5c..a4c82a7c 100644 --- a/rss_reader/loader/exceptions.py +++ b/rss_reader/loader/exceptions.py @@ -1,4 +1,6 @@ +from pandas.errors import EmptyDataError + class EmptyURLError(Exception): """Occurs when the url is empty.""" From 58a5186c9fc722e213b2de6f7be285da5b290dfe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:15:32 +0300 Subject: [PATCH 812/973] create iread_files module in iloader package --- rss_reader/interfaces/iloader/ireader_files.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/interfaces/iloader/ireader_files.py diff --git a/rss_reader/interfaces/iloader/ireader_files.py b/rss_reader/interfaces/iloader/ireader_files.py new file mode 100644 index 00000000..e69de29b From 3195f420565a61d0bf41dce9e7924508b716a851 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:18:28 +0300 Subject: [PATCH 813/973] create IReadFile class --- rss_reader/interfaces/iloader/ireader_files.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/interfaces/iloader/ireader_files.py b/rss_reader/interfaces/iloader/ireader_files.py index e69de29b..12ce56cc 100644 --- a/rss_reader/interfaces/iloader/ireader_files.py +++ b/rss_reader/interfaces/iloader/ireader_files.py @@ -0,0 +1,7 @@ + + +class IReadFile(ABC): + @abstractmethod + def read(file: str, + index_col: str = 'index', encoding='utf-8') -> DataFrame: + pass From 18be3093573287c067c011127b966c5be7921838 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:18:52 +0300 Subject: [PATCH 814/973] import modules --- rss_reader/interfaces/iloader/ireader_files.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/interfaces/iloader/ireader_files.py b/rss_reader/interfaces/iloader/ireader_files.py index 12ce56cc..2014b9b8 100644 --- a/rss_reader/interfaces/iloader/ireader_files.py +++ b/rss_reader/interfaces/iloader/ireader_files.py @@ -1,4 +1,7 @@ +from abc import ABC, abstractmethod +from pandas import DataFrame + class IReadFile(ABC): @abstractmethod From 39ef70cb4e01ab16e57ba80964185c6f4584657a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:24:41 +0300 Subject: [PATCH 815/973] import modules --- rss_reader/loader/reader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py index b93488f3..b2f15e40 100644 --- a/rss_reader/loader/reader.py +++ b/rss_reader/loader/reader.py @@ -2,6 +2,9 @@ from pandas import DataFrame, read_csv from pandas.errors import EmptyDataError +from rss_reader.interfaces.iloader.ireader_files import IReadFile +from .exceptions import DataFileNotFoundError, DataFileEmptyError + class ReaderCSVFile(IReadFile): @staticmethod From 59a5b7b7d9c79a564b7ce07af8ea0d76a69d94a4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:34:16 +0300 Subject: [PATCH 816/973] create AbstractLoaderHandler class --- rss_reader/loader/loader.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 16aae7a1..108b2175 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -15,6 +15,22 @@ log = Logger.get_logger(__name__) +class AbstractLoaderHandler(ILoadHandler): + + _next_handler: Optional[ILoadHandler] = None + + @send_log_of_start_function + def set_next(self, handler: ILoadHandler) -> ILoadHandler: + self._next_handler = handler + return handler + + @send_log_of_start_function + def get_data(self) -> list: + if self._next_handler: + return self._next_handler.get_data() + return None + + class FromWebHandler(IHandler): """Internet data handler.""" From 7726a2500620e25cc880a7276906a97f9405d6f6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:35:07 +0300 Subject: [PATCH 817/973] create FromLocalSTorageHandler class --- rss_reader/loader/loader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 108b2175..187d304e 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -31,6 +31,10 @@ def get_data(self) -> list: return None +class FromLocalSTorageHandler(AbstractLoaderHandler): + pass + + class FromWebHandler(IHandler): """Internet data handler.""" From 27affeffdbabece952f875da6bc611857a97a5a5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:38:55 +0300 Subject: [PATCH 818/973] override get_data method in the class FromLocalSTorageHandler class --- rss_reader/loader/loader.py | 39 ++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 187d304e..2d3123b9 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -32,7 +32,44 @@ def get_data(self) -> list: class FromLocalSTorageHandler(AbstractLoaderHandler): - pass + + def __init__(self, file: str, request: Dict[str, str]) -> None: + self._file = file + self._request = request + + def get_data(self) -> list: + date = self._request.get('date') + if date: + + raw_data = ReaderCSVFile.read(self._file) + + try: + dt = DateConverter().date(date) + except ValueError as e: + raise ValueError('Wrong time format') from e + + bc = BaseComponent() + fined_data = SortByEqual('item.pubDate', dt.__str__(), bc) + + source = self._request.get('source') + if source: + fined_data = SortByEqual( + 'link', source, fined_data) + + limit = self._request.get('limit') + if limit: + fined_data = LimitRecords(limit, fined_data) + + fined_data = fined_data.operation(raw_data) + + if fined_data.empty: + raise DataEmptyError( + 'There is no data to provide for the current date.') + + data = self._convert_to_dict(fined_data) + return data + else: + return super().get_data() class FromWebHandler(IHandler): From cd814372cf3c271751e790aca2d264af78c8c1b9 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:39:54 +0300 Subject: [PATCH 819/973] create _convert_to_dict method --- rss_reader/loader/loader.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 2d3123b9..7c70d463 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -71,6 +71,43 @@ def get_data(self) -> list: else: return super().get_data() + def _convert_to_dict(self, raw_data: DataFrame) -> list: + l_item = [] + + def new_item(): + new_source = {} + new_source['title_web_resource'] = v.get('title_web_resource') + new_source['link'] = link + new_source['items'] = [item] + l_item.append(new_source) + + for i, v in raw_data.iterrows(): + + v.replace(nan, None, inplace=True) + link = v.get('link') + + item = {} + item['title'] = v.get('item.title') + item['link'] = v.get('item.link') + item['pubDate'] = v.get('item.pubDate') + item['source'] = v.get('item.source') + + content = {} + content['url'] = v.get('item.content.url') + content['title'] = v.get('item.content.title') + item['content'] = content + + if not l_item: + new_item() + else: + for i in l_item: + if link == i.get('link'): + i['items'].append(item) + break + else: + new_item() + return l_item + class FromWebHandler(IHandler): """Internet data handler.""" From 6eaf7530b14bf84006fe5f39504822a47ef8e795 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:57:33 +0300 Subject: [PATCH 820/973] create ILoadHandler class --- rss_reader/interfaces/iloader/iloader.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index e2b45918..c52e77af 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -26,3 +26,13 @@ def get_data(self, tag_name: str, :rtype: dict """ pass + + +class ILoadHandler(ABC): + @abstractmethod + def set_next(self, handler: ILoadHandler) -> ILoadHandler: + pass + + @abstractmethod + def get_data(self, data: dict): + pass From 5c706b9b97b3f6bc2593f57b0170c8202630d4d1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 06:58:44 +0300 Subject: [PATCH 821/973] import annotations to the iloader module --- rss_reader/interfaces/iloader/iloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index c52e77af..4694f531 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -1,6 +1,6 @@ """This module contains a set of interfaces for the loader.""" - +from __future__ import annotations from abc import ABC, abstractmethod From 2f2b64ca06debe85e77612b302f3984c2800934d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 07:04:26 +0300 Subject: [PATCH 822/973] import modules to the loader module --- rss_reader/loader/loader.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 7c70d463..b49381a4 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -5,11 +5,20 @@ """ -from rss_reader.interfaces.iloader.iloader import IHandler +from typing import Dict, Optional +from numpy import nan +from pandas import DataFrame + +from rss_reader.interfaces.iloader.iloader import IHandler, ILoadHandler from rss_reader.interfaces.icrawler.icrawler import ICrawler from rss_reader.interfaces.iparser.iparser import IParser from rss_reader.logger.logger import Logger from rss_reader.decorator.decorator import send_log_of_start_function +from rss_reader.date_converter.date_converter import DateConverter + +from .reader import ReaderCSVFile +from .decorators import BaseComponent, SortByEqual, LimitRecords +from .exceptions import DataEmptyError log = Logger.get_logger(__name__) From 7951b36672ed373bf7f32e7f5dba3430dcb4dc26 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 23:29:21 +0300 Subject: [PATCH 823/973] add a default value to the source argument --- rss_reader/starter/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index 84de663a..f43f4496 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -33,7 +33,8 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('source', nargs='?', - default='https://news.yahoo.com/rss/', + default='', # на бв + # default='https://news.yahoo.com/rss/', help='RSS URL') parser.add_argument('--version', From d13ad1626938477849ba383af9922bf773de23fd Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 23:34:58 +0300 Subject: [PATCH 824/973] remove error trapping in __main__ --- rss_reader/__main__.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 29863d53..bbeed5cd 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -19,25 +19,25 @@ def main(): log.debug("Create a Starter object.") s = Starter(args) - try: - log.info("Start the program.") - s.run() - except NonNumericError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) - except BadURLError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) - except Exception as e: - s = ('Sorry, we have to stop working. Something went wrong.' - 'We are terribly sorry.') - print(s) - log.error(e) - finally: - log.info("Stop the program.") - exit() + # try: + log.info("Start the program.") + s.run() + # except NonNumericError as e: + # print(f'Sorry, we have to stop working. Because:') + # print(f'\t {e}') + # log.error(e) + # except BadURLError as e: + # print(f'Sorry, we have to stop working. Because:') + # print(f'\t {e}') + # log.error(e) + # except Exception as e: + # s = ('Sorry, we have to stop working. Something went wrong.' + # 'We are terribly sorry.') + # print(s) + # log.error(e) + # finally: + # log.info("Stop the program.") + # exit() if __name__ == "__main__": From 59f67029ad245ffe970b429185d28918bc80fa74 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 23:36:02 +0300 Subject: [PATCH 825/973] return the value of the data_concat variable --- rss_reader/data_converter/data_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index a21bda41..520a30c2 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -20,6 +20,7 @@ def concat_data(self, data, local_data) -> Optional[DataFrame]: ignore_index=True) data_concat.drop_duplicates(keep='first', inplace=True, ignore_index=True) + return data_concat def _convert_date(self, df: DataFrame, column_name: str, format: str = '%Y-%m-%d', From 143dc50b94bca8214e10bf419f3bba2407085ccc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Tue, 28 Jun 2022 23:57:07 +0300 Subject: [PATCH 826/973] change attribute type to List[dict] --- rss_reader/interfaces/iviewer/iviewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iviewer/iviewer.py b/rss_reader/interfaces/iviewer/iviewer.py index b85913e6..5b7a6f04 100644 --- a/rss_reader/interfaces/iviewer/iviewer.py +++ b/rss_reader/interfaces/iviewer/iviewer.py @@ -3,6 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import List class IViewHandler(ABC): @@ -20,7 +21,7 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: pass @abstractmethod - def show(self, data: dict) -> None: + def show(self, data: List[dict]) -> None: """Show data. :param data: Dictionary with data to be printed on the screen. From 7fd632b5d57d78de3f0616e744f33826dc89f4da Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:00:27 +0300 Subject: [PATCH 827/973] remove the data argument --- rss_reader/interfaces/iloader/iloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index 4694f531..0344682a 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -34,5 +34,5 @@ def set_next(self, handler: ILoadHandler) -> ILoadHandler: pass @abstractmethod - def get_data(self, data: dict): + def get_data(self): pass From ca6f3aad17ab6b1239353046a3cc546e5df697d5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:01:31 +0300 Subject: [PATCH 828/973] set the return type --- rss_reader/interfaces/iloader/iloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index 0344682a..9b34bc71 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -34,5 +34,5 @@ def set_next(self, handler: ILoadHandler) -> ILoadHandler: pass @abstractmethod - def get_data(self): + def get_data(self) -> list: pass From ec14bdc5f443f56f103d494858d78f2228982421 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:04:11 +0300 Subject: [PATCH 829/973] load data from file --- rss_reader/loader/loader.py | 50 ++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index b49381a4..83e3e8f0 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -5,7 +5,7 @@ """ -from typing import Dict, Optional +from typing import Dict, List, Optional from numpy import nan from pandas import DataFrame @@ -15,10 +15,12 @@ from rss_reader.logger.logger import Logger from rss_reader.decorator.decorator import send_log_of_start_function from rss_reader.date_converter.date_converter import DateConverter +from rss_reader.parser.exceptions import EmptyListError + from .reader import ReaderCSVFile from .decorators import BaseComponent, SortByEqual, LimitRecords -from .exceptions import DataEmptyError +from .exceptions import DataEmptyError, EmptyURLError log = Logger.get_logger(__name__) @@ -53,7 +55,7 @@ def get_data(self) -> list: raw_data = ReaderCSVFile.read(self._file) try: - dt = DateConverter().date(date) + dt = DateConverter().date_convert(date) except ValueError as e: raise ValueError('Wrong time format') from e @@ -76,6 +78,7 @@ def get_data(self) -> list: 'There is no data to provide for the current date.') data = self._convert_to_dict(fined_data) + return data else: return super().get_data() @@ -115,6 +118,7 @@ def new_item(): break else: new_item() + return l_item @@ -129,6 +133,11 @@ class FromWebHandler(IHandler): } def __init__(self, + tag_name: str, + title_tag: str, + channel_link: str, + source: str, + limit: Optional[int], crawler: ICrawler, parser: IParser) -> None: """Initializer. @@ -140,15 +149,16 @@ def __init__(self, received from the Internet. :type parser: IParser """ + self._tag_name = tag_name + self._title_tag = title_tag + self._channel_linik = channel_link + self._source = source + self._limit = limit self._crawler = crawler self._parser = parser @send_log_of_start_function - def get_data(self, - tag_name: str, - title_tag: str, - source: str, - limit: int) -> dict: + def get_data(self) -> List[dict]: """Return a dictionary with parsed data. :param tag_name: The name of the tag in which the news is stored. @@ -163,25 +173,35 @@ def get_data(self, :return: Dictionary with parsed data. :rtype: dict """ + if not self._source: + raise EmptyURLError('Passed url is empty!') - cr = self._crawler(source) + cr = self._crawler(self._source) response_ = cr.get_data() log.debug('Start creating the parser.') self._parser.create_parser(markup=response_) log.debug('Stop creating the parser.') - log.debug('Start getting parsed data.') - title_text = next(self._parser.get_tags_text( - selector=title_tag)) + log.debug('Start the process of getting the resource title.') + try: + title_tag = self._parser.get_tags_text(selector=self._title_tag) + title_text = next(title_tag) + except EmptyListError: + title_text = None + log.debug('Stop the process of getting the resource title.') + items = self._parser.get_items( - self.template, name=tag_name, limit_elms=limit) - log.debug('Stop getting parsed data.') + self.template, name=self._tag_name, limit_elms=self._limit) + + if not items: + raise DataEmptyError('no news') log.debug('Start generating results.') result = {'title_web_resource': title_text} + result.update({'link': self._source}) items_dict = {'items': items} result.update(items_dict) log.debug('Result was formed.') - return result + return [result] From 628185ef3a698a4fd6f05b68c3e6584234eb5b63 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:06:05 +0300 Subject: [PATCH 830/973] create a loader object --- rss_reader/starter/starter.py | 80 +++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 8d70ef2e..58099f62 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -4,14 +4,17 @@ from bs4 import BeautifulSoup from rss_reader.logger.logger import Logger -from rss_reader.interfaces.iloader.iloader import IHandler -from rss_reader.loader.loader import FromWebHandler +from rss_reader.interfaces.iloader.iloader import IHandler, ILoadHandler +from rss_reader.loader.loader import FromWebHandler, FromLocalSTorageHandler from rss_reader.parser.parser import BeautifulParser from rss_reader.crawler.crawler import SuperCrawler from rss_reader.crawler.exceptions import BadURLError from rss_reader.interfaces.iviewer.iviewer import IViewHandler from rss_reader.viewer.viewer import StandartViewHandler, JSONViewHandler +from rss_reader.interfaces.isaver.isaver import ISaveHandler +from rss_reader.saver.saver import LocalSaveHandler + from .ecxeptions import NonNumericError log = Logger.get_logger(__name__) @@ -31,30 +34,12 @@ def __init__(self, argv: Dict[str, str]) -> None: def run(self) -> None: """Program launch.""" - if self._argv['source']: - log.info("Get the number of requested news.") - - try: - lim = self._argv.get('limit') - limit = int(lim) if lim else None - except ValueError as e: - log.error(e) - raise NonNumericError("--limit has a non-numeric value") from e - - log.info("Number was received.") - - data_handler = self._get_data_from_resource() - try: - data = data_handler.get_data('item', - 'channel > title', - self._argv.get('source'), - limit) - except BadURLError as e: - log.error(e) - raise - - if not data['items']: - data['items'] = [{'no news': 'Sorry, no news'}] + data_handler = self._get_data_from_resource() + try: + data = data_handler.get_data() + except BadURLError as e: + log.error(e) + raise log.info("Start getting the viewer object.") viewer = self._get_viewer(self._argv) @@ -64,11 +49,39 @@ def run(self) -> None: viewer.show(data) log.info("Stop displaying data.") - def _get_data_from_resource(self) -> IHandler: - """Get data handler.""" - web_hendler = FromWebHandler(SuperCrawler, - BeautifulParser(BeautifulSoup)) - return web_hendler + self._get_saver().save(data) + + def _get_limit(self) -> None: + log.info("Get the number of requested news.") + try: + lim = self._argv.get('limit') + limit = int(lim) if lim else None + except ValueError as e: + log.exception(e) + raise NonNumericError("--limit has a non-numeric value") from e + + if limit is not None and limit < 0: + raise ValueError('--limit must be positive') + log.info("Number was received.") + + self._argv['limit'] = limit + + def _get_data_from_resource(self) -> ILoadHandler: + """настроить обработчик иданных""" + + self._get_limit() + + wh = FromWebHandler('item', + 'channel > title', + 'channel > link', + self._argv.get('source'), + self._argv.get('limit'), + SuperCrawler, + BeautifulParser(BeautifulSoup)) + + ls = FromLocalSTorageHandler('local_storage.csv', self._argv) + ls.set_next(wh) + return ls def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: """Get data viewer.""" @@ -76,3 +89,8 @@ def _get_viewer(self, request: Dict[str, str]) -> IViewHandler: json_ = JSONViewHandler(request) json_.set_next(stdout_) return json_ + + def _get_saver(self) -> ISaveHandler: + standart_saver = LocalSaveHandler() + + return standart_saver From 542def9c9aeac9bf90b581bec5e8c2271fd71239 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:08:48 +0300 Subject: [PATCH 831/973] change the show method to show the new data structure --- rss_reader/viewer/viewer.py | 82 ++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index aa2f8d5d..eebcb7d4 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -4,7 +4,7 @@ Each viewer is called in a chain. """ -from typing import Dict, Optional +from typing import Dict, List, Optional from json import dumps from rss_reader.interfaces.iviewer.iviewer import IViewHandler @@ -33,7 +33,7 @@ def set_next(self, handler: IViewHandler) -> IViewHandler: return handler @send_log_of_start_function - def show(self, data: dict) -> None: + def show(self, data: List[dict]) -> None: """Show data. :param data: Dictionary with data to be printed on the screen. @@ -50,56 +50,46 @@ class StandartViewHandler(AbstractViewHandler): Executed when others have failed to process the data. """ - def show(self, data: dict) -> None: - """Show data. + def show(self, data: List[dict]) -> None: + for i in data: + self._show_item(i) - :param data: Dictionary with data to be printed on the screen. - :type data: dict - """ - log.debug("Print title.") - self._get_info(data, "title_web_resource", "\nFeed: ", - alternative='no data', end="\n\n\n") - - log.debug("Start getting news.") + def _show_item(self, data: dict): + """Показать данные""" + self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") items = data.get('items') - log.debug("Start getting news.") - - is_list = isinstance(items, list) - is_now_news = False - - if is_list: + if isinstance(items, list): for i in items: - if "no news" in i: - is_now_news = True - - if is_now_news: - log.info("No news.") - self._get_info(items[0], "no news", "News") - else: - for i in items: - log.debug("Start posting news.") - self._get_info(i, "title", "Title") - self._get_info(i, "source", "Source") - self._get_info(i, "pubDate", "PubDate") - self._get_info(i, "link", "Link") - media_content = i.get("content") - if media_content: - print("Media content:") - self._get_info(media_content, "title", - "[title of media content]") - self._get_info(media_content, "url", - "[source of media content]") - print('\n\n') - log.debug("Stop posting news.") - - def _get_info(self, dict_: dict, attr: str, str_: str, - alternative: str = '', end='\n') -> None: - """Print a string containing data from a dictionary.""" + self._get_info(i, "title", "Title") + self._get_info(i, "source", "Source") + self._get_info(i, "pubDate", "PubDate") + self._get_info(i, "link", "Link") + media_content = i.get("content") + + if not self._is_empty(media_content): + print("Media content:") + self._get_info(media_content, "title", + "[title of media content]") + self._get_info(media_content, "url", + "[source of media content]") + print('\n\n') + elif items: + print(items) + + def _is_empty(self, lst: List[Dict[str, str]]) -> bool: + result = True + for i in lst: + if lst[i] is not None: + result = False + break + + return result + + def _get_info(self, dict_: dict, attr: str, str_: str, end='\n'): + """получить строку с данными из словаря""" x = dict_.get(attr) if x: print(f'{str_}: {x}', end=end) - elif alternative: - print(f'{str_}: {alternative}', end=end) class JSONViewHandler(AbstractViewHandler): From c4e118fdc5ac68a1c441f77f1345ea18372abbac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:17:15 +0300 Subject: [PATCH 832/973] add comments to the sequence of actions in the get_data method --- rss_reader/loader/loader.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 83e3e8f0..47c2a542 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -52,31 +52,38 @@ def get_data(self) -> list: date = self._request.get('date') if date: + # load data from local storage raw_data = ReaderCSVFile.read(self._file) + # convert date to string try: dt = DateConverter().date_convert(date) except ValueError as e: raise ValueError('Wrong time format') from e + # design pattern - decorator bc = BaseComponent() + # sort by date fined_data = SortByEqual('item.pubDate', dt.__str__(), bc) source = self._request.get('source') if source: + # sort by source fined_data = SortByEqual( 'link', source, fined_data) limit = self._request.get('limit') if limit: + # sort by limit fined_data = LimitRecords(limit, fined_data) - + # start execution of the decorator pattern fined_data = fined_data.operation(raw_data) if fined_data.empty: raise DataEmptyError( 'There is no data to provide for the current date.') + # convert to the given dictionary structure data = self._convert_to_dict(fined_data) return data From b270b1db177e0036c8d971f3a519da74259c07f5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:26:48 +0300 Subject: [PATCH 833/973] add a dockstring to the __init__ --- rss_reader/loader/loader.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 47c2a542..19e5b2f2 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -142,18 +142,27 @@ class FromWebHandler(IHandler): def __init__(self, tag_name: str, title_tag: str, - channel_link: str, source: str, limit: Optional[int], crawler: ICrawler, parser: IParser) -> None: """Initializer. + :param tag_name: The name of the tag in which the news is stored. + For example . + :type tag_name: str + :param title_tag: The name of the tag that contains the title. + It is CSS selector. + :type title_tag: str + :param source: News source url. + :type source: str + :param limit: The number of elements to return. + :type limit: Optional[int] :param crawler: Crawler object. Used to get information - from the Internet. + from the Internet. :type crawler: ICrawler :param parser: Parser object. Used to parse information - received from the Internet. + received from the Internet. :type parser: IParser """ self._tag_name = tag_name From 5f4f6dcdf4ff12abf9ffdc8dbdbcd5ca7bf4ee19 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:30:32 +0300 Subject: [PATCH 834/973] remove "channel > link" --- rss_reader/starter/starter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 58099f62..9bed1a39 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -73,7 +73,6 @@ def _get_data_from_resource(self) -> ILoadHandler: wh = FromWebHandler('item', 'channel > title', - 'channel > link', self._argv.get('source'), self._argv.get('limit'), SuperCrawler, From 8471587b195803d5beb095d11364f6938f5634ac Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:33:07 +0300 Subject: [PATCH 835/973] del _channel_linik --- rss_reader/loader/loader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 19e5b2f2..cf91a28f 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -167,7 +167,6 @@ def __init__(self, """ self._tag_name = tag_name self._title_tag = title_tag - self._channel_linik = channel_link self._source = source self._limit = limit self._crawler = crawler From 47463bd04c25273a2023fcdd7f908b2782c970b0 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:37:12 +0300 Subject: [PATCH 836/973] replace IHandler with AbstractLoaderHandler --- rss_reader/loader/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index cf91a28f..e94c2edc 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -129,7 +129,7 @@ def new_item(): return l_item -class FromWebHandler(IHandler): +class FromWebHandler(AbstractLoaderHandler): """Internet data handler.""" template = {'title': 'text', From c67ff00373305b95ea77f50f1071ea7e1da5bbbb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:42:42 +0300 Subject: [PATCH 837/973] add a dockstring to the FromWebHandler --- rss_reader/loader/loader.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index e94c2edc..3b0b9a4c 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -174,23 +174,17 @@ def __init__(self, @send_log_of_start_function def get_data(self) -> List[dict]: - """Return a dictionary with parsed data. + """Return a list with parsed data. - :param tag_name: The name of the tag in which the news is stored. - :type tag_name: str - :param title_tag: The name of the tag in which the name - of the rss resource is stored. - :type title_tag: str - :param source: Resource URL. - :type source: str - :param limit: Number of news items to display. - :type limit: int - :return: Dictionary with parsed data. - :rtype: dict + :raises EmptyURLError: Occurs when the url is empty. + :raises DataEmptyError: Occurs when there is no data. + :return: List with data. + :rtype: List[dict] """ if not self._source: raise EmptyURLError('Passed url is empty!') + # get data from internet cr = self._crawler(self._source) response_ = cr.get_data() @@ -206,12 +200,14 @@ def get_data(self) -> List[dict]: title_text = None log.debug('Stop the process of getting the resource title.') + # get news items = self._parser.get_items( self.template, name=self._tag_name, limit_elms=self._limit) if not items: raise DataEmptyError('no news') + # collect all the data in a dictionary log.debug('Start generating results.') result = {'title_web_resource': title_text} result.update({'link': self._source}) From 7cef685231f2a5258d77d5b028dac5a5a2e418f1 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:48:15 +0300 Subject: [PATCH 838/973] add a dockstring to the get_data --- rss_reader/loader/loader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 3b0b9a4c..aab923b8 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -49,6 +49,13 @@ def __init__(self, file: str, request: Dict[str, str]) -> None: self._request = request def get_data(self) -> list: + """Return a list with parsed data. + + :raises ValueError: Occurs when the date cannot be converted. + :raises DataEmptyError: Occurs when there is no data. + :return: List with data. + :rtype: list + """ date = self._request.get('date') if date: From d7952d8f4c1995cebfe3a84a8fb97ee84f3fe1ca Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:48:52 +0300 Subject: [PATCH 839/973] add a dockstring to the _convert_to_dict --- rss_reader/loader/loader.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index aab923b8..13297977 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -98,6 +98,28 @@ def get_data(self) -> list: return super().get_data() def _convert_to_dict(self, raw_data: DataFrame) -> list: + """Convert to the desired data structure. + + For example: + [ + {'title_web_resource': 'Yahoo News - Latest News & Headlines', + 'link':'https://news.yahoo.com/rss/', + 'items': [ + {'title': 'The media receive copies onched missiles on25 June', + 'link': 'https://news.yahoo.com/medi04.html', + 'pubDate': '2022-06-28', + 'source': 'Ukrayina Pravda', + 'content': + { + 'url': 'https://s.yimg.com/uu/api/re.com/en/ukc321c3475', + 'title': None + } + } + ] + } + ] + """ + l_item = [] def new_item(): From 459c559367c3dbcbc4b68167b344b077d43f045c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:50:08 +0300 Subject: [PATCH 840/973] add a dockstring to the FromLocalSTorageHandler --- rss_reader/loader/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 13297977..e18ae7fe 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -43,6 +43,7 @@ def get_data(self) -> list: class FromLocalSTorageHandler(AbstractLoaderHandler): + """Data loader from local storage.""" def __init__(self, file: str, request: Dict[str, str]) -> None: self._file = file From 5ad0e465b13bd46845d29d93629c532d441d16af Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:52:58 +0300 Subject: [PATCH 841/973] add a docstring to the AbstractLoaderHandler --- rss_reader/loader/loader.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index e18ae7fe..4658e696 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -27,16 +27,30 @@ class AbstractLoaderHandler(ILoadHandler): + """The base class of the handler.""" _next_handler: Optional[ILoadHandler] = None @send_log_of_start_function def set_next(self, handler: ILoadHandler) -> ILoadHandler: + """Set the next viewer in the handler chain. + + :param handler: Next handler. + :type handler: ILoadHandler + :return: Handler. + :rtype: ILoadHandler + """ self._next_handler = handler return handler @send_log_of_start_function def get_data(self) -> list: + """Get the requested data. + + :return: List with data. + :rtype: list + """ + if self._next_handler: return self._next_handler.get_data() return None From f8e807d6d403cda252b0aeb4ac7df13e91d65ebc Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 00:55:01 +0300 Subject: [PATCH 842/973] change dict to List[dict] --- rss_reader/viewer/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index eebcb7d4..6edd0100 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -104,7 +104,7 @@ def __init__(self, request: Dict[str, str]) -> None: """ self._request = request - def show(self, data: dict) -> None: + def show(self, data: List[dict]) -> None: """Display the data as a JSON structure. :param data: Dictionary with data to be printed on the screen. From df69ac4a38721664242ef696aa307e10a320da52 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 05:34:08 +0300 Subject: [PATCH 843/973] fix status --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index 15007be1..b1e49176 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -35,7 +35,7 @@ def get_data(self) -> bytes: r = self._get_response() status = self._get_status(r) if status == 200: - return self._get_content(r) + return status log.error( f'An unsupported HTTP status code returned. (code = {status})') raise FailStatusCodeError(status) From 47efbb477dfd4827d92f63c2b108537bd5d9effe Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 05:41:49 +0300 Subject: [PATCH 844/973] add a dockstring to the StandartViewHandler --- rss_reader/viewer/viewer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index 6edd0100..f8465b29 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -51,11 +51,16 @@ class StandartViewHandler(AbstractViewHandler): """ def show(self, data: List[dict]) -> None: + """Show data. + + :param data: Information td display. + :type data: List[dict] + """ for i in data: self._show_item(i) def _show_item(self, data: dict): - """Показать данные""" + """Show data.""" self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") items = data.get('items') if isinstance(items, list): @@ -86,7 +91,7 @@ def _is_empty(self, lst: List[Dict[str, str]]) -> bool: return result def _get_info(self, dict_: dict, attr: str, str_: str, end='\n'): - """получить строку с данными из словаря""" + """Printing a string from a dictionary""" x = dict_.get(attr) if x: print(f'{str_}: {x}', end=end) From 6cfa405331bfe42b2092d033e45cfc612e4fa8ef Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 20:11:08 +0300 Subject: [PATCH 845/973] fix. replace status with self._get_content(r) --- rss_reader/crawler/crawler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/crawler/crawler.py b/rss_reader/crawler/crawler.py index b1e49176..15007be1 100644 --- a/rss_reader/crawler/crawler.py +++ b/rss_reader/crawler/crawler.py @@ -35,7 +35,7 @@ def get_data(self) -> bytes: r = self._get_response() status = self._get_status(r) if status == 200: - return status + return self._get_content(r) log.error( f'An unsupported HTTP status code returned. (code = {status})') raise FailStatusCodeError(status) From a33bbe93a8dc61fb269cdc9faefee1810df93f63 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 20:12:20 +0300 Subject: [PATCH 846/973] replace log.exception with log.error --- rss_reader/saver/reader_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/reader_files.py b/rss_reader/saver/reader_files.py index 42b57b20..fe3d4eda 100644 --- a/rss_reader/saver/reader_files.py +++ b/rss_reader/saver/reader_files.py @@ -46,7 +46,7 @@ def read_csv_file(self, file: str, except EmptyDataError as e: log.error(f'{file} is empty') except FileNotFoundError as e: - log.exception(f'No such file or directory: {file}') + log.error(f'No such file or directory: {file}') creater.create_file(file) return local_storage From 666df5cdc8f035f3bece5bebcec6ec058e809373 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 20:31:45 +0300 Subject: [PATCH 847/973] add a check for the existence of the list --- rss_reader/viewer/viewer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index f8465b29..a1079f62 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -83,10 +83,11 @@ def _show_item(self, data: dict): def _is_empty(self, lst: List[Dict[str, str]]) -> bool: result = True - for i in lst: - if lst[i] is not None: - result = False - break + if lst: + for i in lst: + if lst[i] is not None: + result = False + break return result From 27ddf59d6008c9efdc1d078afd6a3af2ecc12a58 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 21:15:37 +0300 Subject: [PATCH 848/973] add local storage --- rss_reader/starter/starter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index 9bed1a39..ba69701e 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -11,6 +11,7 @@ from rss_reader.crawler.exceptions import BadURLError from rss_reader.interfaces.iviewer.iviewer import IViewHandler from rss_reader.viewer.viewer import StandartViewHandler, JSONViewHandler +from rss_reader.pathfile.pathfile import PathFile from rss_reader.interfaces.isaver.isaver import ISaveHandler from rss_reader.saver.saver import LocalSaveHandler @@ -19,6 +20,8 @@ log = Logger.get_logger(__name__) +LOCAL_STORAGE = '.rss-reader/local_storage.csv' + class Starter: """A class to represent a starter main program.""" @@ -49,7 +52,11 @@ def run(self) -> None: viewer.show(data) log.info("Stop displaying data.") - self._get_saver().save(data) + # the place where the database of saved queries is stored + local_storage = PathFile().home()/LOCAL_STORAGE + + # save data + self._get_saver().save(data, local_storage) def _get_limit(self) -> None: log.info("Get the number of requested news.") From 54d5f93c4b6c6c1df1892c1e7089a689aaa27958 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 21:17:18 +0300 Subject: [PATCH 849/973] create a folder for local storage --- rss_reader/pathfile/pathfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index 861141c7..e7760dcf 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -16,4 +16,5 @@ def create_file(self, file: str) -> None: :type file: str """ file = Path(file) + Path.mkdir(file.parent, parents=True, exist_ok=True) file.touch(exist_ok=True) From 87868601fcfb754628e6c9ae7c3031831ee63a73 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 21:18:17 +0300 Subject: [PATCH 850/973] create home method --- rss_reader/pathfile/pathfile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/pathfile/pathfile.py b/rss_reader/pathfile/pathfile.py index e7760dcf..4c8b4882 100644 --- a/rss_reader/pathfile/pathfile.py +++ b/rss_reader/pathfile/pathfile.py @@ -18,3 +18,7 @@ def create_file(self, file: str) -> None: file = Path(file) Path.mkdir(file.parent, parents=True, exist_ok=True) file.touch(exist_ok=True) + + def home(self) -> None: + """Return a new path pointing to the user's home directory.""" + return Path.home() From 1d93e8a596b5d3df71a967d119c0bda6326bb1c3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 23:38:09 +0300 Subject: [PATCH 851/973] break the DataConverter class into methods --- rss_reader/data_converter/data_converter.py | 114 +++++++++++++++++--- 1 file changed, 98 insertions(+), 16 deletions(-) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index 520a30c2..b62e7109 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -1,6 +1,7 @@ +"""This module contains a class that works with data stored in local storage.""" - -from typing import Optional +from locale import normalize +from typing import List, Optional from pandas import DataFrame, json_normalize, concat, to_datetime from rss_reader.interfaces.idataconverter.idataconverter import IDataConverter @@ -8,24 +9,105 @@ class DataConverter(IDataConverter): - def concat_data(self, data, local_data) -> Optional[DataFrame]: - """Concat two DataFrames.""" + def concat_data(self, data: List[dict], + local_data: DataFrame, + ignore_index: bool) -> Optional[DataFrame]: + """Concat two DataFrames. - norm_data = json_normalize(data, record_path=['items'], - meta=['title_web_resource', 'link'], - record_prefix="item.") + :param data: Data to be merged with data from local storage. + :type data: List[dict] + :param local_data: DataFrame which is obtained from local storage. + :type local_data: DataFrame + :param ignore_index: Returns True when the argument x is true, + False otherwise. The builtins True and False are the only two + instances of the class bool. The class bool is a subclass of the + class int, and cannot be subclassed. + :type ignore_index: bool + :return: Merged DataFrame. + :rtype: Optional[DataFrame] + """ - norm_data = self._convert_date(norm_data, 'item.pubDate') - data_concat = concat([local_data, norm_data], - ignore_index=True) - data_concat.drop_duplicates(keep='first', inplace=True, - ignore_index=True) + data_concat = concat([local_data, data], + ignore_index=ignore_index) return data_concat - def _convert_date(self, df: DataFrame, column_name: - str, format: str = '%Y-%m-%d', - utc: bool = True) -> DataFrame: - """Convert the date to the desired format.""" + def drop_duplicates_(self, data: DataFrame, + keep: str = "first", + ignore_index: bool = False) -> Optional[DataFrame]: + """Return DataFrame with duplicate rows removed. + + :param data: The DataFrame in which duplicates should be removed. + :type data: DataFrame + :param keep: {'first', 'last', False}, default 'first' + Determines which duplicates (if any) to keep. + - ``first`` : Drop duplicates except for the first occurrence. + - ``last`` : Drop duplicates except for the last occurrence. + - False : Drop all duplicates. + :type keep: str + :param inplace: bool, default False + Whether to drop duplicates in place or to return a copy. + :type inplace: bool + :param ignore_index: bool, default False + If True, the resulting axis will be labeled 0, 1, …, n - 1. + :type ignore_index: bool + :return: DataFrame without duplicates. + :rtype: Optional[DataFrame] + """ + return data.drop_duplicates(keep=keep, + ignore_index=ignore_index) + + def normalize_(self, data: List[dict], record_path: List[str], + meta: List[str], record_prefix: str) -> DataFrame: + """Normalize semi-structured JSON data into a flat table. + + :param data: Data for normalize. + :type data: List[dict] + :param record_path: Path in each object to list of records. If not + passed, data will be assumed to be an array of records. + :type record_path: List[str] + :param meta: Fields to use as metadata for each record in resulting + table. + :type meta: List[str] + :param record_prefix: If True, prefix records with dotted (?) path, + e.g. foo.bar.field if + path to records is ['foo', 'bar']. + :type record_prefix: str + :return: Normalize semi-structured JSON data into a flat table. + :rtype: DataFrame + """ + return json_normalize(data, record_path=record_path, + meta=meta, + record_prefix=record_prefix) + + def convert_date(self, df: DataFrame, column_name: + str, format: str = '%Y-%m-%d', + utc: bool = True) -> DataFrame: + """Convert the date to the desired format. + + :param df: The DataFrame in which to replace. + :type df: DataFrame + :param column_name: The name of the column in which you want to change. + :type column_name: str + :param format: Return a string representing the date, controlled by an + explicit format string. Format codes referring to hours, minutes or + seconds will see 0 values., defaults to '%Y-%m-%d' + :type format: str, optional + :param utc: Control timezone-related parsing, localization and + conversion. + + If True, the function always returns a timezone-aware UTC-localized + Timestamp, Series or DatetimeIndex. To do this, timezone-naive inputs + are localized as UTC, while timezone-aware inputs are converted to UTC. + + If False (default), inputs will not be coerced to UTC. Timezone-naive + inputs will remain naive, while timezone-aware ones will keep their + time offsets. Limitations exist for mixed offsets (typically, daylight + savings), see Examples section for details., defaults to True + + :type utc: bool + :return: DataFrame with the converted date. + :rtype: DataFrame + """ df[column_name] = to_datetime(df.get(column_name), utc=utc) df[column_name] = df.get(column_name).dt.date.apply( From 3219cecbbaff71927bf6702b776065fd627bb8c7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 23:39:33 +0300 Subject: [PATCH 852/973] add a dockstring to the DataConverter class --- rss_reader/data_converter/data_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rss_reader/data_converter/data_converter.py b/rss_reader/data_converter/data_converter.py index b62e7109..60cff242 100644 --- a/rss_reader/data_converter/data_converter.py +++ b/rss_reader/data_converter/data_converter.py @@ -8,6 +8,7 @@ class DataConverter(IDataConverter): + """Transforms data from one state to another.""" def concat_data(self, data: List[dict], local_data: DataFrame, From da159d4a91cf66496eda979d56a391f311fd37de Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 23:40:59 +0300 Subject: [PATCH 853/973] add methods to the IDataConverter class --- .../idataconverter/idataconverter.py | 98 +++++++++++++++++-- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/rss_reader/interfaces/idataconverter/idataconverter.py b/rss_reader/interfaces/idataconverter/idataconverter.py index 1edb38bf..a3018b57 100644 --- a/rss_reader/interfaces/idataconverter/idataconverter.py +++ b/rss_reader/interfaces/idataconverter/idataconverter.py @@ -11,15 +11,99 @@ class IDataConverter(ABC): @abstractmethod def concat_data(self, data: List[dict], - local_data: str) -> Optional[DataFrame]: - """Get normalized data. + local_data: DataFrame, + ignore_index: bool) -> Optional[DataFrame]: + """Concat two DataFrames. - :param data: Data for concatenation. + :param data: Data to be merged with data from local storage. :type data: List[dict] - :param local_data: The name of the file where the locally saved data - is stored. - :type local_data: str - :return: DataFrame with merged data. Duplicates are removed. + :param local_data: DataFrame which is obtained from local storage. + :type local_data: DataFrame + :param ignore_index: Returns True when the argument x is true, + False otherwise. The builtins True and False are the only two + instances of the class bool. The class bool is a subclass of the + class int, and cannot be subclassed. + :type ignore_index: bool + :return: Merged DataFrame. :rtype: Optional[DataFrame] """ pass + + @abstractmethod + def drop_duplicates_(self, data: DataFrame, + keep: str = "first", + ignore_index: bool = False) -> Optional[DataFrame]: + """Return DataFrame with duplicate rows removed. + + :param data: The DataFrame in which duplicates should be removed. + :type data: DataFrame + :param keep: {'first', 'last', False}, default 'first' + Determines which duplicates (if any) to keep. + - ``first`` : Drop duplicates except for the first occurrence. + - ``last`` : Drop duplicates except for the last occurrence. + - False : Drop all duplicates. + :type keep: str + :param inplace: bool, default False + Whether to drop duplicates in place or to return a copy. + :type inplace: bool + :param ignore_index: bool, default False + If True, the resulting axis will be labeled 0, 1, …, n - 1. + :type ignore_index: bool + :return: DataFrame without duplicates. + :rtype: Optional[DataFrame] + """ + pass + + @abstractmethod + def normalize_(self, data: List[dict], record_path: List[str], + meta: List[str], record_prefix: str) -> DataFrame: + """Normalize semi-structured JSON data into a flat table. + + :param data: Data for normalize. + :type data: List[dict] + :param record_path: Path in each object to list of records. If not + passed, data will be assumed to be an array of records. + :type record_path: List[str] + :param meta: Fields to use as metadata for each record in resulting + table. + :type meta: List[str] + :param record_prefix: If True, prefix records with dotted (?) path, + e.g. foo.bar.field if + path to records is ['foo', 'bar']. + :type record_prefix: str + :return: Normalize semi-structured JSON data into a flat table. + :rtype: DataFrame + """ + pass + + @abstractmethod + def convert_date(self, df: DataFrame, column_name: + str, format: str = '%Y-%m-%d', + utc: bool = True) -> DataFrame: + """Convert the date to the desired format. + + :param df: The DataFrame in which to replace. + :type df: DataFrame + :param column_name: The name of the column in which you want to change. + :type column_name: str + :param format: Return a string representing the date, controlled by an + explicit format string. Format codes referring to hours, minutes or + seconds will see 0 values., defaults to '%Y-%m-%d' + :type format: str, optional + :param utc: Control timezone-related parsing, localization and + conversion. + + If True, the function always returns a timezone-aware UTC-localized + Timestamp, Series or DatetimeIndex. To do this, timezone-naive inputs + are localized as UTC, while timezone-aware inputs are converted to UTC. + + If False (default), inputs will not be coerced to UTC. Timezone-naive + inputs will remain naive, while timezone-aware ones will keep their + time offsets. Limitations exist for mixed offsets (typically, daylight + savings), see Examples section for details., defaults to True + + :type utc: bool + :return: DataFrame with the converted date. + :rtype: DataFrame + """ + pass From d56adc4a88e98e15dfdfc7e2d285607bd623296f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 23:46:15 +0300 Subject: [PATCH 854/973] Break a method into multiple submethods --- rss_reader/saver/saver.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index a0537b41..ae0635be 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -40,13 +40,30 @@ def save(self, data: List[dict], file: str) -> None: class LocalSaveHandler(AbstractSaveHandler): - def save(self, data: List[dict], file: str = 'local_storage.csv') -> None: + """Stores data locally.""" + + def save(self, data: List[dict], file: str) -> None: + """Save data. + + :param data: Dictionary with data to save. + :type data: List[dict] + :param file: File save path. + :type file: str + """ local_data = ReaderFiles().read_csv_file(file, 'index', PathFile()) try: - norm_data = DataConverter().concat_data(data, local_data) + dc = DataConverter() + norm_data = dc.normalize_(data, record_path=['items'], + meta=['title_web_resource', 'link'], + record_prefix="item.") + norm_data = dc.convert_date(norm_data, 'item.pubDate') + data_concat = dc.concat_data(local_data, norm_data, + ignore_index=True) + data_to_file = dc.drop_duplicates_(data_concat, keep='first', + ignore_index=True) except NotImplementedError as e: local_data = None - norm_data = pd.DataFrame() + data_to_file = pd.DataFrame() - norm_data.to_csv(file, encoding='utf-8', index_label='index') + data_to_file.to_csv(file, encoding='utf-8', index_label='index') From f9956612067a60c34c854d47834f894f8fc042b3 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Wed, 29 Jun 2022 23:47:54 +0300 Subject: [PATCH 855/973] add local storage path --- rss_reader/starter/starter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/starter/starter.py b/rss_reader/starter/starter.py index ba69701e..2ff19380 100644 --- a/rss_reader/starter/starter.py +++ b/rss_reader/starter/starter.py @@ -84,8 +84,9 @@ def _get_data_from_resource(self) -> ILoadHandler: self._argv.get('limit'), SuperCrawler, BeautifulParser(BeautifulSoup)) + local_storage = PathFile().home()/LOCAL_STORAGE - ls = FromLocalSTorageHandler('local_storage.csv', self._argv) + ls = FromLocalSTorageHandler(local_storage, self._argv) ls.set_next(wh) return ls From 3245d71b2638e7cfb2bddd2cf3f483cecad8a14f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:08:54 +0300 Subject: [PATCH 856/973] add a dockstrint to the decorators module --- rss_reader/loader/decorators.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 311e3d60..94a05e38 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -1,5 +1,7 @@ -from abc import ABC, abstractmethod +"""This module implements the decorator pattern.""" + +from abc import ABC, abstractmethod from pandas import DataFrame From 9a0dbe83e94f36a15a6b078b12cf46ef8b54569d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:13:10 +0300 Subject: [PATCH 857/973] add a docstring to the ILoadHandler class --- rss_reader/interfaces/iloader/iloader.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rss_reader/interfaces/iloader/iloader.py b/rss_reader/interfaces/iloader/iloader.py index 9b34bc71..c91d3fc0 100644 --- a/rss_reader/interfaces/iloader/iloader.py +++ b/rss_reader/interfaces/iloader/iloader.py @@ -29,10 +29,24 @@ def get_data(self, tag_name: str, class ILoadHandler(ABC): + """Interface for loaders data.""" + @abstractmethod def set_next(self, handler: ILoadHandler) -> ILoadHandler: + """Set the next viewer in the handler chain. + + :param handler: Next handler. + :type handler: ILoadHandler + :return: Handler. + :rtype: ILoadHandler + """ pass @abstractmethod def get_data(self) -> list: + """Get the requested data. + + :return: List with data. + :rtype: list + """ pass From 1dbe81100dc265e90fc5e549037f65e7915e83e4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:16:41 +0300 Subject: [PATCH 858/973] add a dockstring to the SortByEqual class --- rss_reader/loader/decorators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/loader/decorators.py b/rss_reader/loader/decorators.py index 94a05e38..579d496b 100644 --- a/rss_reader/loader/decorators.py +++ b/rss_reader/loader/decorators.py @@ -66,6 +66,8 @@ def operation(self, data: DataFrame) -> DataFrame: class SortByEqual(Decorator): + """A decorator that selects DataFrame elements by column value.""" + def __init__(self, search_column: str, criterion: str, component: IComponent) -> None: """Initializer. From 300344cfd4ad98d1c333ebda4543ec7397d46e2b Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:22:17 +0300 Subject: [PATCH 859/973] add an addition to the docstring --- rss_reader/loader/loader.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 4658e696..bf6350e3 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -124,7 +124,23 @@ def _convert_to_dict(self, raw_data: DataFrame) -> list: 'link': 'https://news.yahoo.com/medi04.html', 'pubDate': '2022-06-28', 'source': 'Ukrayina Pravda', - 'content': + 'content': + { + 'url': 'https://s.yimg.com/uu/api/re.com/en/ukc321c3475', + 'title': None + } + } + ] + } + + {'title_web_resource': 'Новости ООН - Здравоохранение', + 'link':'https://news.un.org/feed/subscribe/ru/news/topic/health/feed/rss.xml', + 'items': [ + {'title': 'итуация с безопасностью дорожного движения ухудшается', + 'link': 'https://news.yahoo.com/medi04.html', + 'pubDate': '2022-06-28', + 'source': 'UN', + 'content': { 'url': 'https://s.yimg.com/uu/api/re.com/en/ukc321c3475', 'title': None @@ -170,6 +186,8 @@ def new_item(): else: new_item() + print(f'-> {l_item}') + return l_item From beeaaf4c2654c18142359378c918d304009e8e08 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:29:23 +0300 Subject: [PATCH 860/973] add a docstrings to the reader module --- rss_reader/loader/reader.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rss_reader/loader/reader.py b/rss_reader/loader/reader.py index b2f15e40..d71101d0 100644 --- a/rss_reader/loader/reader.py +++ b/rss_reader/loader/reader.py @@ -1,3 +1,5 @@ +"""This module contains data readers from files.""" + from pandas import DataFrame, read_csv from pandas.errors import EmptyDataError @@ -7,9 +9,28 @@ class ReaderCSVFile(IReadFile): + """Reader CSV File.""" + @staticmethod def read(file: str, index_col: str = 'index', encoding='utf-8') -> DataFrame: + """Read CSV File. + + :param file: File name. + :type file: str + :param index_col: Column(s) to use as the row labels of the DataFrame, + either given as string name or column index. If a sequence of + int / str is given, a MultiIndex is used, defaults to 'index'. + :type index_col: str, optional + :param encoding: Encoding to use for UTF when reading/writing + (ex. ‘utf-8’). , defaults to 'utf-8' + :type encoding: str, optional + :raises DataFileNotFoundError: Occurs when the file is not found. + :raises DataFileEmptyError: Occurs when there is no data in the + uploaded file. + :return: The data read from the file as a DataFrame. + :rtype: DataFrame + """ try: raw_data = read_csv(file, index_col=index_col, encoding=encoding) except FileNotFoundError as e: From fc1c62fb50da5c64e4591a51e2f2de2d20b644de Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:35:44 +0300 Subject: [PATCH 861/973] fix. remove redundant arguments --- rss_reader/starter/tests/test_starter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/starter/tests/test_starter.py b/rss_reader/starter/tests/test_starter.py index 79e2438e..4fe6b133 100644 --- a/rss_reader/starter/tests/test_starter.py +++ b/rss_reader/starter/tests/test_starter.py @@ -33,7 +33,7 @@ def test_starter_run_BadURLError(monkeypatch): def mock_get_status(*args, **kwargs): class Mock_BadURLError: @classmethod - def get_data(self, a, b, c, d): + def get_data(self): raise BadURLError return Mock_BadURLError() From 9c06c729b21256726281a6ded439a3f5a6de883e Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:45:59 +0300 Subject: [PATCH 862/973] convert the test structure to the new function --- rss_reader/tests/loader/test_loader.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rss_reader/tests/loader/test_loader.py b/rss_reader/tests/loader/test_loader.py index e066bdbd..c1708c0e 100644 --- a/rss_reader/tests/loader/test_loader.py +++ b/rss_reader/tests/loader/test_loader.py @@ -32,7 +32,8 @@ def mock_parser(mocker): def test_get_items_empty(mock_crawler, mock_parser): """Check that a dictionary of the given structure is returned""" - h = FromWebHandler(SuperCrawler, BeautifulParser('s')) - r = h.get_data('a', 'b', 'c', 1) - assert r == {'title_web_resource': 'Title!', - 'items': [{'mock_item': 1}]} + h = FromWebHandler('a', 'b', 'c', 1, SuperCrawler, BeautifulParser('s')) + r = h.get_data() + assert r == [{'title_web_resource': 'Title!', + 'link': 'c', + 'items': [{'mock_item': 1}]}] From ac580f2b363d5b82a41ff22f18eb16631438c8bb Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 00:52:26 +0300 Subject: [PATCH 863/973] put the dictionary in the list --- rss_reader/viewer/tests/test_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/viewer/tests/test_viewer.py b/rss_reader/viewer/tests/test_viewer.py index 4c055f50..e2c0356e 100644 --- a/rss_reader/viewer/tests/test_viewer.py +++ b/rss_reader/viewer/tests/test_viewer.py @@ -8,7 +8,7 @@ def test_StandartViewHandler_show(capsys): """check that the show method prints the correct data to stdout.""" - data = {'title_web_resource': 'mock_show'} + data = [{'title_web_resource': 'mock_show'}] json_obj = StandartViewHandler() json_obj.show(data) out, err = capsys.readouterr() From 5d2aaa0e7ae4188fe5b1de97a301b7a4852fdd8a Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 01:04:49 +0300 Subject: [PATCH 864/973] remove comments --- rss_reader/starter/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index f43f4496..e6b566d4 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -33,8 +33,7 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: parser.add_argument('source', nargs='?', - default='', # на бв - # default='https://news.yahoo.com/rss/', + default='', help='RSS URL') parser.add_argument('--version', From db4a901065ce9166a1d7ef10c082c08f7990a2ee Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 01:58:30 +0300 Subject: [PATCH 865/973] fix. delet print --- rss_reader/loader/loader.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index bf6350e3..23025db0 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -186,8 +186,6 @@ def new_item(): else: new_item() - print(f'-> {l_item}') - return l_item @@ -250,6 +248,24 @@ def get_data(self) -> List[dict]: cr = self._crawler(self._source) response_ = cr.get_data() +# response_ = ''' +# +# +# Yahoo News - Latest News & Headlines +# https://www.yahoo.com/news +# +# ‘Tonight, it’s my turn’: Biden takes spill while getting off bike after beach ride +# +# 2022-06-18T14:37:24Z +# +# biden-takes-spill-while-getting-143724765.html +# +# +# +# +# +# ''' + log.debug('Start creating the parser.') self._parser.create_parser(markup=response_) log.debug('Stop creating the parser.') From b2e79c63add631e271db58f4fb2daf9bb623e9aa Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 01:59:25 +0300 Subject: [PATCH 866/973] create display_information.rst --- docs/source/display_information.rst | 115 ++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/source/display_information.rst diff --git a/docs/source/display_information.rst b/docs/source/display_information.rst new file mode 100644 index 00000000..45d1eb3b --- /dev/null +++ b/docs/source/display_information.rst @@ -0,0 +1,115 @@ +Display of information. +======================= + +This part of the documentation describes how rss-reader displays information. +----------------------------------------------------------------------------- + +Information display: +-------------------- +Normally. +~~~~~~~~~~~ + * When all data is available. + + .. figure:: /images/output.jpg + + * When there is missing data. + + .. figure:: /images/output-empty.jpg + + * When the date parameter is used and the result contains different news sources. + + .. figure:: /images/date_parametr.jpg + + +With the given \-\-json parameter: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * When all data is available. + + .. code-block:: JSON + + [ + { + "title_web_resource": "Yahoo News - Latest News & Headlines", + "link": "https://news.yahoo.com/rss/", + "items": + [ + { + "title": "1955 warrant family seeks", + "link": "https://news.yahoo.com/1955-war79.html", + "pubDate": "2022-06-29T19:41:30Z", + "source": "Associated Press", + "content": { + "url": "https://s.yimg.com/uu/api/res/1.2/z8bf83", + "title": null + } + } + ] + } + ] + + * When there is missing data. + + .. code-block:: JSON + + [ + { + "title_web_resource": "Yahoo News - Latest News Headlines", + "link": "https://news.yahoo.com/rss/", + "items": + [ + { + "title": "Biden takes", + "link": "", + "pubDate": "2022-06-18T14:37:24Z", + "source": "", + "content": { + "url": "", + "title": null + } + } + ] + } + ] + + +With the given \-\-date and \-\-json parameter: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: JSON + + [ + { + "title_web_resource": "Yahoo News - Latest News & Headlines", + "link": "https://news.yahoo.com/rss/", + "items": + [ + { + "title": "Hong Kongs", + "link": "https://news.yahoo.com/hong.html", + "pubDate": "2022-06-01", + "source": "Associated Press", + "content": { + "url": "https://s.yimg.com/uu/api/reen/ap.org/0c", + "title": null + } + } + ] + }, + { + "title_web_resource": "Новости ООН - Здравоохранение", + "link": "https://news.un.org/feed/rss.xml", + "items": + [ + { + "title": "ВОЗ: необходимы антибиотики нового поколения", + "link": "https://news.un.org/08.html", + "pubDate": "2022-06-01", + "source": "Associated Press", + "content": { + "url": "https://s.yimg.com/uu/api-rg/0725fc", + "title": null + } + } + ] + } + ] \ No newline at end of file From b6ad86167273542edae4476290c9bb5635bc984d Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:00:25 +0300 Subject: [PATCH 867/973] create local_storage.rst --- docs/source/local_storage.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docs/source/local_storage.rst diff --git a/docs/source/local_storage.rst b/docs/source/local_storage.rst new file mode 100644 index 00000000..ed8ecd82 --- /dev/null +++ b/docs/source/local_storage.rst @@ -0,0 +1,11 @@ +Local storage. +======================= + +This part of the documentation describes how rss-reader stores local information. +--------------------------------------------------------------------------------- + +All information is stored in 'home directory'/.rss-reader/local_storage.csv + +Representation of the local storage file. + +.. figure:: /images/local_storage.jpg \ No newline at end of file From 4dcf51b10d9251c5def047a20f5cf1d1253d0da8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:01:34 +0300 Subject: [PATCH 868/973] add display_information and local_storage to the toctree --- docs/source/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 5d39d604..cc2c1d37 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,6 +11,8 @@ Welcome to rss-reader's documentation! :caption: Contents: start_program.rst + display_information.rst + local_storage.rst tests.rst From 1223aed0bf85c92b8a14d6624041e265a441ea08 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:02:58 +0300 Subject: [PATCH 869/973] delete the Information display section --- docs/source/start_program.rst | 57 +---------------------------------- 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/docs/source/start_program.rst b/docs/source/start_program.rst index 61ffd79e..87e66b7d 100644 --- a/docs/source/start_program.rst +++ b/docs/source/start_program.rst @@ -14,59 +14,4 @@ The program supports the following keys: * \-\-json - Print result as JSON in stdout. * \-\-limit - Limit news topics if this parameter provided. * \-\-version - Print version info. - - -Information display: --------------------- -Normally. -~~~~~~~~~~~ - * When all data is available. - - .. figure:: /images/output.jpg - - * When there is missing data. - - .. figure:: /images/output-empty.jpg - - -With the given \-\-json parameter: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * When all data is available. - - .. code-block:: JSON - - { - 'title_web_resource': 'Yahoo News - Latest News & Headlines', - 'items': - [ - { - 'title': 'Wisconsin election investigator says he deleted records', - 'link': 'https://news.yahoo.com/wisconsin999.html', - 'pubDate': '2022-06-23T15:52:47Z', - 'source': 'Associated Press', - 'content': { - 'url':'https://s.yimg.com/uu/api5f67f7c68', - 'title': None} - } - ] - } - - * When there is missing data. - - .. code-block:: JSON - - { - 'title_web_resource': 'Yahoo News - Latest News & Headlines', - 'items': - [ - { - 'title': 'Wisconsin election investigator says he deleted records', - 'link': '', - 'pubDate': '2022-06-23T15:52:47Z', - 'source': '', - 'content': { - 'url':'', - 'title': None} - } - ] - } + * \-\-date - Search for news on a specified date. Date in the format Y-m-d (for example: 20191206). From 8283115d18e7d4e8644142c2b5cfdf35c674777f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:03:47 +0300 Subject: [PATCH 870/973] add date_parametr.jpg --- docs/source/images/date_parametr.jpg | Bin 0 -> 149677 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/images/date_parametr.jpg diff --git a/docs/source/images/date_parametr.jpg b/docs/source/images/date_parametr.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ec627e5588b7edecd8d35e4f262378a079a991dd GIT binary patch literal 149677 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 Date: Thu, 30 Jun 2022 02:04:11 +0300 Subject: [PATCH 871/973] add local_storage.jpg --- docs/source/images/local_storage.jpg | Bin 0 -> 159671 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/images/local_storage.jpg diff --git a/docs/source/images/local_storage.jpg b/docs/source/images/local_storage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce492989eae7e1775b5edad79d5bf1823adc9346 GIT binary patch literal 159671 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

MG{&SvN<4ip8?t=1z6 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�^&va*6>LU+*;Yl!T+kOPd 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

x8gF(Koi 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 nQ#`W-vLS>a&jezP-;^vbDQ|4#ousPIQ- literal 0 HcmV?d00001 From 24015febc88701b3bdf0adf145b431c64fc3326f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:07:47 +0300 Subject: [PATCH 872/973] comment out the error trapping block --- rss_reader/__main__.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index bbeed5cd..29863d53 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -19,25 +19,25 @@ def main(): log.debug("Create a Starter object.") s = Starter(args) - # try: - log.info("Start the program.") - s.run() - # except NonNumericError as e: - # print(f'Sorry, we have to stop working. Because:') - # print(f'\t {e}') - # log.error(e) - # except BadURLError as e: - # print(f'Sorry, we have to stop working. Because:') - # print(f'\t {e}') - # log.error(e) - # except Exception as e: - # s = ('Sorry, we have to stop working. Something went wrong.' - # 'We are terribly sorry.') - # print(s) - # log.error(e) - # finally: - # log.info("Stop the program.") - # exit() + try: + log.info("Start the program.") + s.run() + except NonNumericError as e: + print(f'Sorry, we have to stop working. Because:') + print(f'\t {e}') + log.error(e) + except BadURLError as e: + print(f'Sorry, we have to stop working. Because:') + print(f'\t {e}') + log.error(e) + except Exception as e: + s = ('Sorry, we have to stop working. Something went wrong.' + 'We are terribly sorry.') + print(s) + log.error(e) + finally: + log.info("Stop the program.") + exit() if __name__ == "__main__": From a8eee10b0a3ab741576592d4d122bbbb297eeeb8 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:19:19 +0300 Subject: [PATCH 873/973] add log to the StandartViewHandler --- rss_reader/viewer/viewer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rss_reader/viewer/viewer.py b/rss_reader/viewer/viewer.py index a1079f62..8f80e520 100644 --- a/rss_reader/viewer/viewer.py +++ b/rss_reader/viewer/viewer.py @@ -57,14 +57,19 @@ def show(self, data: List[dict]) -> None: :type data: List[dict] """ for i in data: + log.debug('start displaying new block news.') self._show_item(i) + log.debug('stop displaying new block news.') def _show_item(self, data: dict): """Show data.""" + log.debug('start getting title.') self._get_info(data, "title_web_resource", "\nFeed: ", end="\n\n\n") + log.debug('stop getting title.') items = data.get('items') if isinstance(items, list): for i in items: + log.debug('start getting new news.') self._get_info(i, "title", "Title") self._get_info(i, "source", "Source") self._get_info(i, "pubDate", "PubDate") @@ -78,6 +83,7 @@ def _show_item(self, data: dict): self._get_info(media_content, "url", "[source of media content]") print('\n\n') + log.debug('stop getting new news.') elif items: print(items) From aa0eba6477c27831ae77890ef4c14360491a4d19 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:20:22 +0300 Subject: [PATCH 874/973] delet comment --- rss_reader/loader/loader.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/rss_reader/loader/loader.py b/rss_reader/loader/loader.py index 23025db0..326e3698 100644 --- a/rss_reader/loader/loader.py +++ b/rss_reader/loader/loader.py @@ -248,24 +248,6 @@ def get_data(self) -> List[dict]: cr = self._crawler(self._source) response_ = cr.get_data() -# response_ = ''' -# -# -# Yahoo News - Latest News & Headlines -# https://www.yahoo.com/news -# -# ‘Tonight, it’s my turn’: Biden takes spill while getting off bike after beach ride -# -# 2022-06-18T14:37:24Z -# -# biden-takes-spill-while-getting-143724765.html -# -# -# -# -# -# ''' - log.debug('Start creating the parser.') self._parser.create_parser(markup=response_) log.debug('Stop creating the parser.') From 974b817e59f9ac302a2ec68fc1cad66f5d2c5e30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:21:22 +0300 Subject: [PATCH 875/973] catch the EmptyURLError in the __nmain__ mdule --- rss_reader/__main__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 29863d53..2215e153 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -6,6 +6,7 @@ from rss_reader.logger.logger import Logger from rss_reader.starter.ecxeptions import NonNumericError from rss_reader.crawler.exceptions import BadURLError +from rss_reader.loader.exceptions import EmptyURLError def main(): @@ -30,11 +31,15 @@ def main(): print(f'Sorry, we have to stop working. Because:') print(f'\t {e}') log.error(e) + except EmptyURLError as e: + print(f'Sorry, we have to stop working. Because:') + print(f'\t {e}') + log.error(e) except Exception as e: s = ('Sorry, we have to stop working. Something went wrong.' 'We are terribly sorry.') print(s) - log.error(e) + log.error(e) finally: log.info("Stop the program.") exit() From f632dcab80a81a115a10748432523429bdb42e30 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:28:37 +0300 Subject: [PATCH 876/973] add pandas to required install --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d761b4f8..ae0183e9 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ "requests==2.28.0", "beautifulsoup4 == 4.11.1", "lxml==4.9.0", + "pandas==1.4.3" ], # packages=['FT'], packages=find_packages(exclude=['test*']), From 1b8247484c64996d2093c3e6fdc60d4a622fe613 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:31:30 +0300 Subject: [PATCH 877/973] change assembly version to 0.0.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ae0183e9..fb4624d9 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="rss_reader", - version="0.0.2", + version="0.0.3", author="Andrey Ozerets", description="Super rss-reader.", long_description=read_me_description, From 6f9dc5860537ff3ab36bd663540e10ba26541463 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:42:17 +0300 Subject: [PATCH 878/973] catch the DataFileNotFoundError in the __main__ module --- rss_reader/__main__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 2215e153..c9f319b7 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -6,7 +6,7 @@ from rss_reader.logger.logger import Logger from rss_reader.starter.ecxeptions import NonNumericError from rss_reader.crawler.exceptions import BadURLError -from rss_reader.loader.exceptions import EmptyURLError +from rss_reader.loader.exceptions import EmptyURLError, DataFileNotFoundError def main(): @@ -35,11 +35,16 @@ def main(): print(f'Sorry, we have to stop working. Because:') print(f'\t {e}') log.error(e) + + except DataFileNotFoundError as e: + print(f'Sorry, we have to stop working. Because:') + print(f'\t {e}') + log.error(e) except Exception as e: s = ('Sorry, we have to stop working. Something went wrong.' 'We are terribly sorry.') print(s) - log.error(e) + log.error(e) finally: log.info("Stop the program.") exit() From 98615201968e0403cc34631dcc85496173901fb7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:48:31 +0300 Subject: [PATCH 879/973] catch the DataEmptyError in the __main__ module --- rss_reader/__main__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index c9f319b7..5c1a2f4e 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -6,7 +6,7 @@ from rss_reader.logger.logger import Logger from rss_reader.starter.ecxeptions import NonNumericError from rss_reader.crawler.exceptions import BadURLError -from rss_reader.loader.exceptions import EmptyURLError, DataFileNotFoundError +from rss_reader.loader.exceptions import EmptyURLError, DataFileNotFoundError, DataEmptyError def main(): @@ -35,7 +35,10 @@ def main(): print(f'Sorry, we have to stop working. Because:') print(f'\t {e}') log.error(e) - + except DataEmptyError as e: + print(f'Sorry, we have to stop working. Because:') + print(f'\t {e}') + log.error(e) except DataFileNotFoundError as e: print(f'Sorry, we have to stop working. Because:') print(f'\t {e}') From 0fce209303d03e2c3cb68982b2994b9afbd83424 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 02:54:04 +0300 Subject: [PATCH 880/973] move the display of the error message to one function --- rss_reader/__main__.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py index 5c1a2f4e..9b31df21 100644 --- a/rss_reader/__main__.py +++ b/rss_reader/__main__.py @@ -20,29 +20,24 @@ def main(): log.debug("Create a Starter object.") s = Starter(args) + def print_exc(e: Exception): + print(f'Sorry, we have to stop working. Because:') + print(f'\t {e}') + log.error(e) + try: log.info("Start the program.") s.run() except NonNumericError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) + print_exc(e) except BadURLError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) + print_exc(e) except EmptyURLError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) + print_exc(e) except DataEmptyError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) + print_exc(e) except DataFileNotFoundError as e: - print(f'Sorry, we have to stop working. Because:') - print(f'\t {e}') - log.error(e) + print_exc(e) except Exception as e: s = ('Sorry, we have to stop working. Something went wrong.' 'We are terribly sorry.') From 3a007b91fb02bdb0c1ef64732e611eba05461107 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:25:56 +0300 Subject: [PATCH 881/973] add --to-html argument to an argparse --- rss_reader/starter/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/starter/base.py b/rss_reader/starter/base.py index e6b566d4..064f449b 100644 --- a/rss_reader/starter/base.py +++ b/rss_reader/starter/base.py @@ -56,6 +56,10 @@ def init_arguments_functionality(args=None) -> Dict[str, str]: help='Search for news on a specified date.\ Date in the format Y-m-d (for example: 20191206).' ) + parser.add_argument('--to-html', + help='Specify the path where to save the resulting\ + data in the html file.' + ) namespace_ = parser.parse_args(args) From b5e387e29d253dfd0fb12f52de7bd9fb1a9e7374 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:34:39 +0300 Subject: [PATCH 882/973] create to_html package --- rss_reader/saver/to_html/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/saver/to_html/__init__.py diff --git a/rss_reader/saver/to_html/__init__.py b/rss_reader/saver/to_html/__init__.py new file mode 100644 index 00000000..e69de29b From c591f1bdc1067b8476ce9cf350e97e4edf895542 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:35:37 +0300 Subject: [PATCH 883/973] create to_html module --- rss_reader/saver/to_html/to_html.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/saver/to_html/to_html.py diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py new file mode 100644 index 00000000..e69de29b From e6a8467bfefe06a4119ee8d7484c919bda408ff7 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:37:37 +0300 Subject: [PATCH 884/973] import AbstractSaveHandler into the to_html module --- rss_reader/saver/to_html/to_html.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py index e69de29b..61b2e82e 100644 --- a/rss_reader/saver/to_html/to_html.py +++ b/rss_reader/saver/to_html/to_html.py @@ -0,0 +1,3 @@ + + +from ..saver import AbstractSaveHandler From f21ea9297ae31210a4424d602c9448ffc0d22459 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:38:32 +0300 Subject: [PATCH 885/973] create HTMLSaveHandler class --- rss_reader/saver/to_html/to_html.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py index 61b2e82e..9ee726ce 100644 --- a/rss_reader/saver/to_html/to_html.py +++ b/rss_reader/saver/to_html/to_html.py @@ -1,3 +1,7 @@ from ..saver import AbstractSaveHandler + + +class HTMLSaveHandler(AbstractSaveHandler): + pass From ba6128ff22a2d1e35ecb4881588cbf147aa4fb38 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:48:23 +0300 Subject: [PATCH 886/973] create save mrthod in the HTMLSaveHandler class --- rss_reader/saver/to_html/to_html.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py index 9ee726ce..631c995b 100644 --- a/rss_reader/saver/to_html/to_html.py +++ b/rss_reader/saver/to_html/to_html.py @@ -4,4 +4,5 @@ class HTMLSaveHandler(AbstractSaveHandler): - pass + def save(self, data: List[dict], file: str) -> None: + pass From 169a02421717d50f04daf65a7a97b685b9ab614c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:49:08 +0300 Subject: [PATCH 887/973] import List into the to_html module --- rss_reader/saver/to_html/to_html.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py index 631c995b..f6493c09 100644 --- a/rss_reader/saver/to_html/to_html.py +++ b/rss_reader/saver/to_html/to_html.py @@ -1,5 +1,7 @@ +from typing import List + from ..saver import AbstractSaveHandler From c6c46c47fc15de643a888665812fcd084db427c6 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:52:01 +0300 Subject: [PATCH 888/973] override __init__ method in the HTMLSaveHandler class --- rss_reader/saver/to_html/to_html.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py index f6493c09..bdd4f744 100644 --- a/rss_reader/saver/to_html/to_html.py +++ b/rss_reader/saver/to_html/to_html.py @@ -6,5 +6,14 @@ class HTMLSaveHandler(AbstractSaveHandler): + def __init__(self, request: Dict[str, str]) -> None: + """Initializer. + + :param request: A dictionary in which there may be a key + by which this handler will work. + :type request: Dict[str, str] + """ + self._request = request + def save(self, data: List[dict], file: str) -> None: pass From 80095691d4a5a98ad0650c4ba260a714746e08da Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:52:42 +0300 Subject: [PATCH 889/973] import Dict to the to_html module --- rss_reader/saver/to_html/to_html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/to_html/to_html.py b/rss_reader/saver/to_html/to_html.py index bdd4f744..c4ba0496 100644 --- a/rss_reader/saver/to_html/to_html.py +++ b/rss_reader/saver/to_html/to_html.py @@ -1,6 +1,6 @@ -from typing import List +from typing import Dict, List from ..saver import AbstractSaveHandler From 7194da720189b5a5a5910c5679ff5a8a8bfb370f Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:53:29 +0300 Subject: [PATCH 890/973] create main.html file --- rss_reader/saver/to_html/templates/main.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rss_reader/saver/to_html/templates/main.html diff --git a/rss_reader/saver/to_html/templates/main.html b/rss_reader/saver/to_html/templates/main.html new file mode 100644 index 00000000..e69de29b From 2d1de1324d9941a12a45068feaea4a353e67eda5 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 07:58:42 +0300 Subject: [PATCH 891/973] create a basic html template --- rss_reader/saver/to_html/templates/main.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/rss_reader/saver/to_html/templates/main.html b/rss_reader/saver/to_html/templates/main.html index e69de29b..d0e2bd32 100644 --- a/rss_reader/saver/to_html/templates/main.html +++ b/rss_reader/saver/to_html/templates/main.html @@ -0,0 +1,19 @@ + + + + + + + + + + + +

+
+
+
+ + + \ No newline at end of file From 4d3b3f55ef0b2a745fe2909f54b6b00758ea641c Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 08:18:28 +0300 Subject: [PATCH 892/973] fix. call the save method instead of show --- rss_reader/saver/saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss_reader/saver/saver.py b/rss_reader/saver/saver.py index ae0635be..a5457dd0 100644 --- a/rss_reader/saver/saver.py +++ b/rss_reader/saver/saver.py @@ -36,7 +36,7 @@ def save(self, data: List[dict], file: str) -> None: :type file: str """ if self._next_handler: - return self._next_handler.show(data) + return self._next_handler.save(data, file) class LocalSaveHandler(AbstractSaveHandler): From f88f14b498e6c72b20e745e62e2f7a8b1480b672 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 15:08:21 +0300 Subject: [PATCH 893/973] fix. remove file argument --- rss_reader/interfaces/isaver/isaver.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rss_reader/interfaces/isaver/isaver.py b/rss_reader/interfaces/isaver/isaver.py index fe9319b7..befe0e74 100644 --- a/rss_reader/interfaces/isaver/isaver.py +++ b/rss_reader/interfaces/isaver/isaver.py @@ -20,12 +20,10 @@ def set_next(self, handler: ISaveHandler) -> ISaveHandler: pass - def save(self, data: List[dict], file: str) -> None: + def save(self, data: List[dict]) -> None: """Save data. :param data: Dictionary with data to save. :type data: dict - :param file: File for save. - :type file: str """ pass From 5ec16f604a7dbf3ec57e7527bd924a7891b402c4 Mon Sep 17 00:00:00 2001 From: Andrey Ozerets Date: Thu, 30 Jun 2022 15:10:10 +0300 Subject: [PATCH 894/973] create a news display template in html --- rss_reader/saver/to_html/templates/main.html | 46 +++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/rss_reader/saver/to_html/templates/main.html b/rss_reader/saver/to_html/templates/main.html index d0e2bd32..7e2d6331 100644 --- a/rss_reader/saver/to_html/templates/main.html +++ b/rss_reader/saver/to_html/templates/main.html @@ -2,7 +2,7 @@ - +

Se?T1GuNYI?^MRe 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