-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
279 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
requests >= 2.31.0, < 3 | ||
ExceptionMan == 0.0.0.dev15 | ||
MDit == 0.0.0.dev15 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
from pylinks.exception.base import PyLinksException | ||
from pylinks.exception.base import PyLinksError | ||
from pylinks.exception import media_type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
"""Custom exceptions raised by the package.""" | ||
|
||
from __future__ import annotations as _annotations | ||
|
||
from typing import TYPE_CHECKING as _TYPE_CHECKING | ||
|
||
import mdit as _mdit | ||
from pylinks.exception import PyLinksError | ||
|
||
if _TYPE_CHECKING: | ||
from typing import Any, Callable | ||
from requests import PreparedRequest, Request, Response | ||
from requests.exceptions import RequestException | ||
|
||
|
||
class WebAPIError(PyLinksError): | ||
"""Base Exception class for all web API exceptions.""" | ||
pass | ||
|
||
|
||
class WebAPIRequestError(WebAPIError): | ||
def __init__(self, request_error: RequestException): | ||
self.request = request_error.request | ||
self.response = request_error.response | ||
details = [] | ||
if self.request: | ||
details.append(_process_request(self.request)) | ||
if self.response: | ||
summary, response_details = _process_response(self.response) | ||
details.append(response_details) | ||
|
||
super().__init__( | ||
title="Web API Request Error", | ||
intro=str(request_error), | ||
details=_mdit.block_container(*details) if details else None, | ||
) | ||
return | ||
|
||
|
||
class WebAPIStatusCodeError(WebAPIError): | ||
""" | ||
Base Exception class for web API status code related exceptions. | ||
By default, raised when status code is in range [400, 600). | ||
""" | ||
|
||
def __init__(self, response: Response): | ||
self.request = response.request | ||
self.response = response | ||
response_summary, response_details = _process_response(response) | ||
details = _mdit.block_container( | ||
_process_request(self.request), | ||
response_details, | ||
) | ||
super().__init__( | ||
title="Web API Status Code Error", | ||
intro=response_summary, | ||
details=details, | ||
) | ||
return | ||
|
||
|
||
class WebAPITemporaryStatusCodeError(WebAPIStatusCodeError): | ||
""" | ||
Exception class for status code errors related to temporary issues. | ||
By default, raised when status code is in (408, 429, 500, 502, 503, 504). | ||
""" | ||
pass | ||
|
||
|
||
class WebAPIPersistentStatusCodeError(WebAPIStatusCodeError): | ||
""" | ||
Exception class for status code errors related to persistent issues. | ||
By default, raised when status code is in range [400, 600), | ||
but not in (408, 429, 500, 502, 503, 504). | ||
""" | ||
pass | ||
|
||
|
||
class WebAPIValueError(WebAPIError): | ||
""" | ||
Exception class for response value errors. | ||
""" | ||
|
||
def __init__(self, response_value: Any, response_verifier: Callable[[Any], bool]): | ||
self.response_value = response_value | ||
self.response_verifier = response_verifier | ||
error_msg = ( | ||
f"Response verifier function {response_verifier} failed to verify {response_value}." | ||
) | ||
super().__init__( | ||
title="Web API Response Verification Error", | ||
intro=error_msg, | ||
) | ||
return | ||
|
||
|
||
def _process_response(response: Response): | ||
# Decode error reason from server | ||
# This part is adapted from `requests` library; See PR #3538 on their GitHub | ||
if isinstance(response.reason, bytes): | ||
try: | ||
reason = response.reason.decode("utf-8") | ||
except UnicodeDecodeError: | ||
reason = response.reason.decode("iso-8859-1") | ||
else: | ||
reason = response.reason | ||
|
||
response_info = [] | ||
response_summary = _mdit.element.field_list() | ||
side = "Client" if response.status_code < 500 else "Server" | ||
for title, value in ( | ||
("Status Code", response.status_code), | ||
("Side", side), | ||
("Reason", reason), | ||
("URL", response.url), | ||
): | ||
if value: | ||
response_summary.append( | ||
title=title, | ||
body=_mdit.element.code_span(str(value)), | ||
) | ||
if response_summary.content.elements(): | ||
response_info.append(response_summary) | ||
if response.text: | ||
response_info.append( | ||
_mdit.element.code_block(response.text, caption="Content") | ||
) | ||
summary = f"HTTP {response.status_code} error ({side.lower()} side) from {response.url}: {reason}" | ||
return summary, _mdit.element.dropdown( | ||
title="Response", | ||
body=response_info, | ||
icon="📥" | ||
) | ||
|
||
|
||
def _process_request(request: Request | PreparedRequest): | ||
request_info = [] | ||
request_summary = _mdit.element.field_list() | ||
for title, attr_name in ( | ||
("Method", "method"), | ||
("URL", "url"), | ||
): | ||
value = getattr(request, attr_name, None) | ||
if value: | ||
request_summary.append( | ||
title=title, | ||
body=_mdit.element.code_span(value), | ||
) | ||
if request_summary.content.elements(): | ||
request_info.append(request_summary) | ||
for title, attr_name in ( | ||
("Data", "data"), | ||
("JSON", "json"), | ||
("Parameters", "params"), | ||
("Body", "body"), | ||
): | ||
value = getattr(request, attr_name, None) | ||
if value: | ||
request_info.append( | ||
_mdit.element.code_block(str(value), caption=title) | ||
) | ||
return _mdit.element.dropdown( | ||
title="Request", | ||
body=request_info, | ||
icon="📤" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,50 @@ | ||
"""PyLinks base exception.""" | ||
|
||
from pathlib import Path as _Path | ||
from __future__ import annotations as _annotations | ||
from typing import TYPE_CHECKING as _TYPE_CHECKING | ||
from functools import partial as _partial | ||
|
||
from exceptionman import ReporterException as _ReporterException | ||
import mdit as _mdit | ||
|
||
class PyLinksException(Exception): | ||
if _TYPE_CHECKING: | ||
from pathlib import Path | ||
|
||
|
||
class PyLinksError(_ReporterException): | ||
"""Base exception for PyLinks. | ||
All exceptions raised by PyLinks inherit from this class. | ||
""" | ||
def __init__(self, message: str): | ||
self.message = message | ||
super().__init__(message) | ||
def __init__( | ||
self, | ||
title: str, | ||
intro, | ||
details = None, | ||
): | ||
sphinx_config = {"html_title": "PyLinks Error Report"} | ||
sphinx_target_config = _mdit.target.sphinx( | ||
renderer=_partial( | ||
_mdit.render.sphinx, | ||
config=_mdit.render.get_sphinx_config(sphinx_config) | ||
) | ||
) | ||
report = _mdit.document( | ||
heading=title, | ||
body={"intro": intro}, | ||
section={"details": _mdit.document(heading="Details", body=details)} if details else None, | ||
target_configs_md={"sphinx": sphinx_target_config}, | ||
) | ||
super().__init__(report=report) | ||
return | ||
|
||
|
||
class PyLinksFileNotFoundError(PyLinksException): | ||
class PyLinksFileNotFoundError(PyLinksError): | ||
"""File not found error.""" | ||
def __init__(self, path: str | _Path): | ||
msg = f"No file found at input path '{path}.'" | ||
super().__init__(message=msg) | ||
def __init__(self, path: Path): | ||
super().__init__( | ||
title="File Not Found Error", | ||
intro=_mdit.inline_container("No file found at input path ", _mdit.element.code_span(str(path))), | ||
) | ||
self.path = path | ||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,34 @@ | ||
from pylinks.exception import PyLinksException as _PyLinksException | ||
import mdit as _mdit | ||
|
||
from pylinks.exception import PyLinksError as _PyLinksError | ||
|
||
class PyLinksMediaTypeParseError(_PyLinksException): | ||
|
||
class PyLinksMediaTypeParseError(_PyLinksError): | ||
"""Error parsing a media type.""" | ||
def __init__(self, message: str, media_type: str): | ||
msg = f"Failed to parse media type '{media_type}': {message}" | ||
super().__init__(message=msg) | ||
self.message = message | ||
def __init__(self, problem: str, media_type: str): | ||
super().__init__( | ||
title="Media Type Parse Error", | ||
intro=_mdit.inline_container( | ||
"Failed to parse media type ", | ||
_mdit.element.code_span(media_type), | ||
". ", | ||
problem, | ||
) | ||
) | ||
self.problem = problem | ||
self.media_type = media_type | ||
return | ||
|
||
|
||
class PyLinksMediaTypeGuessError(_PyLinksException): | ||
class PyLinksMediaTypeGuessError(_PyLinksError): | ||
"""Error guessing the media type of a data URI.""" | ||
def __init__(self, path: str): | ||
msg = f"Failed to guess the media type of '{path}'." | ||
super().__init__(message=msg) | ||
super().__init__( | ||
title="Media Type Guess Error", | ||
intro=_mdit.inline_container( | ||
"Failed to guess the media type of the file at path ", | ||
_mdit.element.code_span(path), | ||
) | ||
) | ||
self.path = path | ||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,20 @@ | ||
from pathlib import Path as _Path | ||
import mdit as _mdit | ||
|
||
from pylinks.exception import PyLinksException as _PyLinksException | ||
from pylinks.exception import PyLinksError as _PyLinksError | ||
|
||
|
||
class PyLinksDataURIParseError(_PyLinksException): | ||
class PyLinksDataURIParseError(_PyLinksError): | ||
"""Error parsing a data URI.""" | ||
def __init__(self, message: str, data_uri: str): | ||
msg = f"Failed to parse data URI '{data_uri}': {message}" | ||
super().__init__(message=msg) | ||
self.message = message | ||
def __init__(self, problem: str, data_uri: str): | ||
super().__init__( | ||
title="Data URI Parse Error", | ||
intro=_mdit.inline_container( | ||
"Failed to parse data URI ", | ||
_mdit.element.code_span(data_uri), | ||
". ", | ||
problem, | ||
) | ||
) | ||
self.problem = problem | ||
self.data_uri = data_uri | ||
return |
Oops, something went wrong.