diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..7cd5b8e --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +[flake8] +format = html +htmldir = flake8-report +count = True +max-complexity = 10 +statistics = True +max-line-length = 127 diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 9a04fd2..700fdea 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -26,14 +26,27 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest + pip install flake8 flake8-html pytest pytest-html if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --format=html --htmldir=flake8-report # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --format=html --htmldir=flake8-report + - name: Upload HTML Flake8 report + uses: actions/upload-artifact@v4 + with: + name: flake8-html-report + path: flake8-report/ + retention-days: 1 - name: Test with pytest run: | - pytest -vv + pytest -v --html=pytest-report.html --self-contained-html + - name: Upload HTML Pytest report + uses: actions/upload-artifact@v4 + with: + name: pytest-html-report + path: pytest-report.html + retention-days: 1 + diff --git a/.gitignore b/.gitignore index 6236b5c..237ad66 100644 --- a/.gitignore +++ b/.gitignore @@ -197,4 +197,8 @@ cython_debug/ .idea # user generated files -*output.log \ No newline at end of file +*output.log + +# Flake8 +flake8-report + diff --git a/src/asynchronous/runner/concurrent.py b/src/asynchronous/runner/concurrent.py index 1b20a2c..a6ce3b3 100644 --- a/src/asynchronous/runner/concurrent.py +++ b/src/asynchronous/runner/concurrent.py @@ -5,11 +5,13 @@ from asynchronous.runner.task.myasynctask import custom_async_task from asynchronous.runner.operation.myoperations import first_operation, second_operation + # this coroutine runs both runner concurrently with asyncio.gather() async def running_concurrently(): await asyncio.gather(custom_async_task(first_operation, 3), custom_async_task(second_operation, 2)) + # starts the event loop, running and waiting for both runner to complete def main(): - asyncio.run(running_concurrently()) \ No newline at end of file + asyncio.run(running_concurrently()) diff --git a/src/asynchronous/runner/operation/myoperations.py b/src/asynchronous/runner/operation/myoperations.py index 2b19bbc..0357801 100644 --- a/src/asynchronous/runner/operation/myoperations.py +++ b/src/asynchronous/runner/operation/myoperations.py @@ -2,8 +2,10 @@ Definition of two simple operations. """ + def first_operation(): print("First operation") + def second_operation(): print("Second operation") diff --git a/src/asynchronous/runner/sequential.py b/src/asynchronous/runner/sequential.py index b1a994a..772e40f 100644 --- a/src/asynchronous/runner/sequential.py +++ b/src/asynchronous/runner/sequential.py @@ -5,11 +5,14 @@ from asynchronous.runner.task.myasynctask import custom_async_task from asynchronous.runner.operation.myoperations import first_operation, second_operation -# runs two runner sequentially, waiting for one to finish before starting the next + +# runs two runner sequentially, waiting for +# one to finish before starting the next async def running_sequentially(): await custom_async_task(first_operation, 3) await custom_async_task(second_operation, 2) + # executes both runner, totaling around 5 seconds of runtime def main(): asyncio.run(running_sequentially()) diff --git a/src/asynchronous/runner/task/myasynctask.py b/src/asynchronous/runner/task/myasynctask.py index 51f7121..4e224cd 100644 --- a/src/asynchronous/runner/task/myasynctask.py +++ b/src/asynchronous/runner/task/myasynctask.py @@ -1,5 +1,6 @@ import asyncio + async def custom_async_task(func, sleep_time): """ Custom asynchronous task (coroutine) @@ -9,4 +10,4 @@ async def custom_async_task(func, sleep_time): print(f"Execution of {func.__name__} started") func() await asyncio.sleep(sleep_time) - print(f"Execution of {func.__name__} completed") \ No newline at end of file + print(f"Execution of {func.__name__} completed") diff --git a/src/asynchronous/tests/usage_test.py b/src/asynchronous/tests/usage_test.py index 8b1d415..8159e72 100644 --- a/src/asynchronous/tests/usage_test.py +++ b/src/asynchronous/tests/usage_test.py @@ -17,6 +17,7 @@ def test_sequential_run(mock_print): mock_print.assert_has_calls(order_of_calls, any_order=False) + @patch('builtins.print', create=True) def test_concurrent_run(mock_print): concurrent.main() diff --git a/src/asynchronous/usage1.py b/src/asynchronous/usage1.py index 6207c22..6f8fe9d 100644 --- a/src/asynchronous/usage1.py +++ b/src/asynchronous/usage1.py @@ -1,3 +1,4 @@ from asynchronous.runner import sequential -sequential.main() \ No newline at end of file + +sequential.main() diff --git a/src/asynchronous/usage2.py b/src/asynchronous/usage2.py index cbfa328..faeca34 100644 --- a/src/asynchronous/usage2.py +++ b/src/asynchronous/usage2.py @@ -1,3 +1,4 @@ from asynchronous.runner import concurrent -concurrent.main() \ No newline at end of file + +concurrent.main() diff --git a/src/decorator/logging/mylog.py b/src/decorator/logging/mylog.py index 37a3f3f..05b2d37 100644 --- a/src/decorator/logging/mylog.py +++ b/src/decorator/logging/mylog.py @@ -8,13 +8,16 @@ from enum import Enum from datetime import datetime + class Level(Enum): NONE = 0 INFO = 1 DEBUG = 2 + LOG_FILE_NAME = "output.log" + # If you’ve called @name without arguments, then the # decorated function will be passed in as _func. If you’ve # called it with arguments, then _func will be None, and @@ -23,18 +26,23 @@ class Level(Enum): # that you can’t call the remaining arguments as positional arguments. def logme(_func=None, *, level: Level = Level.NONE): def decorated_function(func): - @wraps(func) # this ensures docstring, function name, arguments list, etc. are all copied - # to the wrapped function - instead of being replaced with wrapper's info + # "wraps" ensures docstring, function name, arguments list, + # etc. are all copied to the wrapped function - instead of + # being replaced with wrapper's info + @wraps(func) def wrapper(*args, **kwargs): if show_info(): with open(LOG_FILE_NAME, "a+") as log_file: - log_file.writelines("[{}] Executing function: {}\n".format(get_current_timestamp(), func.__name__)) + log_file.writelines("[{}] Executing function: {}\n".format( + get_current_timestamp(), func.__name__)) result = func(*args, **kwargs) if show_debug(): with open(LOG_FILE_NAME, "a+") as log_file: - log_file.writelines("[{}] Result of function {} is: {}\n".format(get_current_timestamp(), func.__name__, result)) + log_file.writelines("[{}] Result of function {} is: {}\n" + .format(get_current_timestamp(), + func.__name__, result)) return result @@ -63,4 +71,3 @@ def show_debug(): # Apply the decorator to the function immediately. else: return decorated_function(_func) - diff --git a/src/decorator/tests/mylog_test.py b/src/decorator/tests/mylog_test.py index 61029f8..41305b0 100644 --- a/src/decorator/tests/mylog_test.py +++ b/src/decorator/tests/mylog_test.py @@ -32,6 +32,7 @@ def test_logme_for_none(mock_print, mock_open): # but target function was called mock_print.assert_called_once() + @patch("builtins.open", create=True) @patch("builtins.print", create=True) def test_logme_for_info(mock_print, mock_open): @@ -42,7 +43,8 @@ def test_logme_for_info(mock_print, mock_open): """ mock_print.__name__ = "print" - # logme returns a wrapper function for the passed function, and I am calling it directly + # logme returns a wrapper function for the passed + # function, and I am calling it directly logme(mock_print, level=Level.INFO)() # one write call to the log output @@ -50,6 +52,7 @@ def test_logme_for_info(mock_print, mock_open): # target function was called mock_print.assert_called_once() + @patch("builtins.open", create=True) @patch("builtins.print", create=True) def test_logme_for_debug(mock_print, mock_open): @@ -60,10 +63,11 @@ def test_logme_for_debug(mock_print, mock_open): """ mock_print.__name__ = "print" - # logme returns a wrapper function for the passed function, and I am calling it directly + # logme returns a wrapper function for the passed + # function, and I am calling it directly logme(print, level=Level.DEBUG)() # in this case, two write calls to the log output assert mock_open.call_count == 2 # target function was called - mock_print.assert_called_once() \ No newline at end of file + mock_print.assert_called_once() diff --git a/src/decorator/using_mylog.py b/src/decorator/using_mylog.py index 66b7eb0..245e5cf 100644 --- a/src/decorator/using_mylog.py +++ b/src/decorator/using_mylog.py @@ -3,22 +3,27 @@ """ from logging.mylog import logme, Level + @logme def add(a, b): return a + b + @logme(level=Level.INFO) def subtract(a, b): return a - b + @logme(level=Level.DEBUG) def multiply(a, b): return a * b + @logme def divide(a, b): return a / b + print("Adding two numbers: " + str(add(1, 2))) print("Subtracting two numbers: " + str(subtract(8, 3))) print("Multiplying two numbers: " + str(multiply(5, 6)))