Skip to content

Commit 1c15b44

Browse files
Refactoring exception handling
With this commit I wanted to make sure that modules weren't raising ClickExceptions and instead were raising their own exception types to reducing coupling.
1 parent 989b18d commit 1c15b44

File tree

6 files changed

+44
-28
lines changed

6 files changed

+44
-28
lines changed

latz/cli.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import create_model, validator
66

77
from .commands import search_command, config_group
8-
from .config import get_app_config, BaseAppConfig
8+
from .config import get_app_config, BaseAppConfig, ConfigError
99
from .constants import CONFIG_FILES
1010
from .plugins.manager import get_plugin_manager
1111

@@ -45,7 +45,10 @@ def cli(ctx):
4545

4646
# Creates the actual config object which parses all possible configuration sources
4747
# listed in ``CONFIG_FILES``.
48-
app_config = get_app_config(CONFIG_FILES, AppConfig)
48+
try:
49+
app_config = get_app_config(CONFIG_FILES, AppConfig)
50+
except ConfigError as exc:
51+
raise click.ClickException(str(exc))
4952

5053
# Attach several properties to click's ``ctx`` object so that we have access to it
5154
# in our sub-commands.

latz/commands/config/commands.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from rich import print as rprint
55

66
from ...constants import CONFIG_FILE_CWD, CONFIG_FILE_HOME_DIR
7-
from ...config.main import parse_config_file_as_json, write_config_file
7+
from ...config import parse_config_file_as_json, write_config_file, ConfigError
88
from .validators import validate_and_parse_config_values
99

1010

@@ -42,10 +42,10 @@ def set_command(home, config_values):
4242
# Merge the new values and old values; new overwrites the old
4343
new_config_file_data = {**parsed_config.data, **config_values}
4444

45-
error = write_config_file(new_config_file_data, config_file)
46-
47-
if error:
48-
raise click.ClickException(error)
45+
try:
46+
write_config_file(new_config_file_data, config_file)
47+
except ConfigError as exc:
48+
raise click.ClickException(str(exc))
4949

5050

5151
group.add_command(show_command)

latz/commands/search.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import cast, ContextManager, Any
33

44
import click
5+
import httpx
56
from rich import print as rprint
67

78
from ..image import ImageAPI
@@ -19,7 +20,10 @@ def command(ctx, query: str):
1920
)
2021

2122
with image_api_context_manager(ctx.obj.config) as api:
22-
result_set = api.search(query)
23+
try:
24+
result_set = api.search(query)
25+
except httpx.HTTPError as exc:
26+
raise click.ClickException(str(exc))
2327

2428
for res in result_set.results:
2529
rprint(res)

latz/config/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
from .main import get_app_config # noqa: F401
1+
from .main import ( # noqa: F401
2+
get_app_config,
3+
parse_config_file_as_json,
4+
write_config_file,
5+
ConfigError,
6+
)
27
from .models import BaseAppConfig # noqa: F401

latz/config/main.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
from typing import NamedTuple, Any
99

1010
from pydantic import ValidationError
11-
from click import ClickException
1211

1312
from .models import BaseAppConfig
1413
from .errors import format_validation_error, format_all_validation_errors
1514

1615
logger = logging.getLogger(__name__)
1716

1817

18+
class ConfigError(Exception):
19+
pass
20+
21+
1922
class ParsedConfigFile(NamedTuple):
2023
#: Path to the configuration file
2124
path: Path
@@ -123,11 +126,11 @@ def get_app_config(
123126
Given a sequence of ``paths`` first attempts to parse these as JSON and then
124127
attempts to parse valid JSON objects as ``AppConfig`` objects.
125128
126-
:raises ClickException: Happens when any errors are encountered during config parsing
129+
:raises ConfigError: Happens when any errors are encountered during config parsing
127130
"""
128131
parsed_config_files = parse_config_files(paths)
129132

130-
# No files were found 🤷‍
133+
# No files were found 🤷‍; let's return a default config object
131134
if parsed_config_files is None:
132135
return model_class()
133136

@@ -140,7 +143,7 @@ def get_app_config(
140143

141144
# Fail loudly if any errors
142145
if len(errors) > 0:
143-
raise ClickException(format_all_validation_errors(errors))
146+
raise ConfigError(format_all_validation_errors(errors))
144147

145148
# Gather configs
146149
app_configs = tuple(parsed.model for parsed in parsed_config_files if parsed.model)
@@ -153,14 +156,14 @@ def get_app_config(
153156
return merge_app_configs(app_configs, model_class)
154157

155158

156-
def write_config_file(
157-
config_file_data: dict[str, Any], config_file: Path
158-
) -> str | None:
159+
def write_config_file(config_file_data: dict[str, Any], config_file: Path) -> None:
159160
"""
160-
Attempts to write a
161+
Attempts to write config file and returns the exception as a string if it failed.
162+
163+
:raises ConfigError: Raised when we are not able to write our config file.
161164
"""
162165
try:
163166
with config_file.open("w") as fp:
164167
json.dump(config_file_data, fp, indent=2)
165168
except OSError as exc:
166-
return str(exc)
169+
raise ConfigError(str(exc))

latz/plugins/image/unsplash.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import urllib.parse
44

5-
import click
6-
from httpx import Client, HTTPError, Headers
5+
from httpx import Client, Headers
76
from pydantic import BaseModel, Field
87

98
from ...image import (
@@ -47,24 +46,26 @@ class UnsplashImageAPI(ImageAPI):
4746
#: Base URL for the Unsplash API
4847
base_url = "https://api.unsplash.com/"
4948

50-
def __init__(self, client_id: str, client):
49+
#: Endpoint used for searching images
50+
search_endpoint = "/search/photos"
51+
52+
def __init__(self, client_id: str, client: Client):
5153
"""We use this initialization method to properly configure the ``httpx.Client`` object"""
5254
self._client_id = client_id
5355
self._headers = {"Authorization": f"Client-ID {client_id}"}
54-
self._client: Client = client
56+
self._client = client
5557
self._client.headers = Headers(self._headers)
5658

5759
def search(self, query: str) -> ImageSearchResultSet:
5860
"""
5961
Find images based on a ``search_term`` and return an ``ImageSearchResultSet``
62+
63+
:raises HTTPError: Encountered during problems querying the API
6064
"""
61-
search_url = urllib.parse.urljoin(self.base_url, "/search/photos")
65+
search_url = urllib.parse.urljoin(self.base_url, self.search_endpoint)
6266

63-
try:
64-
resp = self._client.get(search_url, params={"query": query})
65-
resp.raise_for_status()
66-
except HTTPError as exc:
67-
raise click.ClickException(str(exc))
67+
resp = self._client.get(search_url, params={"query": query})
68+
resp.raise_for_status()
6869

6970
json_data = resp.json()
7071

0 commit comments

Comments
 (0)