Skip to content

Commit

Permalink
Merge e947e0f into f959040
Browse files Browse the repository at this point in the history
  • Loading branch information
vaclav-2012 authored May 29, 2020
2 parents f959040 + e947e0f commit fa47ceb
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ We follow [Semantic Versions](https://semver.org/).
- Implement `--debug` CLI option (sets the logging level to `DEBUG`)
- Add class `Config` that provides configuration for the Slow Start Rewatch
- Add a substitution of placeholders to the `Config` class
- Add class `App` - the main application class
- Add class `SlowStartRewatchException` - the base class for exceptions
- Set up exception handling


## Version 0.1.0
Expand Down
27 changes: 26 additions & 1 deletion slow_start_rewatch/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import logging
import sys
import traceback
from logging.config import dictConfig

import click
import structlog

from slow_start_rewatch.app import App
from slow_start_rewatch.config import Config
from slow_start_rewatch.exceptions import SlowStartRewatchException
from slow_start_rewatch.version import distribution_name, version

# Set up logging:
Expand Down Expand Up @@ -58,6 +62,7 @@
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
log = structlog.get_logger()


@click.command()
Expand All @@ -68,7 +73,27 @@ def main(debug) -> None:
if debug:
logging.getLogger().setLevel(logging.DEBUG)

sys.exit(0)
config = Config()

app = App(config)

try:
app.run()
except SlowStartRewatchException as exception:
click.echo(click.style(str(exception), fg="red"), err=True)

if exception.hint:
click.echo(exception.hint)

sys.exit(exception.exit_code)
except Exception:
log.exception("unhandled_exception")
click.echo(
click.style("An unexpected error has occurred:\n", fg="red") +
traceback.format_exc(),
err=True,
)
sys.exit(1)


if __name__ == "__main__":
Expand Down
17 changes: 17 additions & 0 deletions slow_start_rewatch/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-

import click

from slow_start_rewatch.config import Config


class App(object):
"""The main application object."""

def __init__(self, config: Config) -> None:
"""Initialize App."""
self.config = config

def run(self) -> None:
"""Runs the application."""
click.echo("Starting the Slow Start Rewatch...")
18 changes: 18 additions & 0 deletions slow_start_rewatch/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-

from typing import Optional


class SlowStartRewatchException(Exception):
"""An exception that slow_start_rewatch can handle."""

def __init__(self, message: str, hint: Optional[str] = None, exit_code=1):
"""Initialize SlowStartRewatchException."""
super().__init__(message)
self.message = message
self.hint = hint
self.exit_code = exit_code

def __str__(self):
"""Return string representation of the exception."""
return self.message
21 changes: 21 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-

"""
This module is used to provide configuration, fixtures, and plugins for pytest.
It may be also used for extending doctest's context:
1. https://docs.python.org/3/library/doctest.html
2. https://docs.pytest.org/en/latest/doctest.html
"""

from dotty_dict import dotty

from slow_start_rewatch.config import Config


class MockConfig(Config):
"""Simplified version of the Config class."""

def __init__(self, config_data=None) -> None:
"""Initialize MockConfig."""
self.config = dotty(config_data)
13 changes: 13 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from slow_start_rewatch.app import App
from tests.conftest import MockConfig


def test_run_successfully(capsys):
"""Test that the App runs."""
app = App(MockConfig())
app.run()
captured = capsys.readouterr()

assert "Slow Start" in captured.out
54 changes: 51 additions & 3 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
# -*- coding: utf-8 -*-

import logging
from unittest.mock import patch

from click.testing import CliRunner

from slow_start_rewatch.__main__ import main
from slow_start_rewatch.app import App
from slow_start_rewatch.exceptions import SlowStartRewatchException


def test_run_successfully():
@patch.object(App, "run")
def test_run_successfully(mock_run):
"""Test launching the program without params."""
runner = CliRunner()

cli_result = runner.invoke(main)
assert mock_run.call_count == 1
assert cli_result.exit_code == 0
assert logging.getLogger().getEffectiveLevel() == logging.CRITICAL


def test_check_version():
@patch.object(App, "run")
def test_check_version(mock_run):
"""Test the launch with the ``--version`` option."""
runner = CliRunner()

cli_result = runner.invoke(main, ["--version"])
assert cli_result.exit_code == 0
assert "slow-start-rewatch, version" in cli_result.output
assert mock_run.call_count == 0


def test_debug(request):
@patch.object(App, "run")
def test_debug(mock_run, request):
"""
Test the launch with the ``--debug`` option.
Expand All @@ -44,3 +52,43 @@ def test_debug(request):

assert logger.getEffectiveLevel() == logging.DEBUG
assert cli_result.exit_code == 0


@patch.object(App, "run")
def test_handled_exception_with_hint(mock_run):
"""Test the output of a handled exception (with a hint)."""
runner = CliRunner()
mock_run.side_effect = SlowStartRewatchException(
message="The cute app is pouting.",
hint="Give her headpats.",
)

cli_result = runner.invoke(main)
assert cli_result.exit_code == 1
assert "pouting" in cli_result.output
assert "headpats" in cli_result.output


@patch.object(App, "run")
def test_handled_exception_without_hint(mock_run):
"""Test the output of an exception without a hint."""
runner = CliRunner()
mock_run.side_effect = SlowStartRewatchException(
message="The cute app is unreachable.",
)

cli_result = runner.invoke(main)
assert cli_result.exit_code == 1
assert "unreachable" in cli_result.output


@patch.object(App, "run")
def test_unhandled_exception(mock_run):
"""Test the output of an unhandled exception."""
runner = CliRunner()
mock_run.side_effect = Exception("Reddit API is pouting.")

cli_result = runner.invoke(main)
assert cli_result.exit_code == 1
assert "unexpected error" in cli_result.output
assert "pouting" in cli_result.output

0 comments on commit fa47ceb

Please sign in to comment.