Skip to content

Commit

Permalink
Release version 0.0.0.dev28
Browse files Browse the repository at this point in the history
  • Loading branch information
AAriam committed Sep 27, 2024
1 parent bd9ae41 commit 27ac90a
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 129 deletions.
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ namespaces = true
# ----------------------------------------- Project Metadata -------------------------------------
#
[project]
version = "0.0.0.dev27"
version = "0.0.0.dev28"
name = "PyLinks"
dependencies = [
"requests >= 2.31.0, < 3",
"ExceptionMan == 0.0.0.dev15",
"MDit == 0.0.0.dev15",
]
requires-python = ">=3.10"

3 changes: 3 additions & 0 deletions requirements.txt
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
2 changes: 1 addition & 1 deletion src/pylinks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
"""

from pylinks._settings import settings
from pylinks import url, http, api, site, exceptions, uri, media_type, string
from pylinks import url, http, api, site, uri, media_type, string
2 changes: 1 addition & 1 deletion src/pylinks/exception/__init__.py
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
166 changes: 166 additions & 0 deletions src/pylinks/exception/api.py
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="📤"
)
45 changes: 36 additions & 9 deletions src/pylinks/exception/base.py
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
32 changes: 23 additions & 9 deletions src/pylinks/exception/media_type.py
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
21 changes: 14 additions & 7 deletions src/pylinks/exception/uri.py
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
Loading

0 comments on commit 27ac90a

Please sign in to comment.