PyLinks 0.0.0.dev27__tar.gz → 0.0.0.dev28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/PKG-INFO +3 -1
- pylinks-0.0.0.dev28/README.md +4 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/pyproject.toml +3 -1
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/PyLinks.egg-info/PKG-INFO +3 -1
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/PyLinks.egg-info/SOURCES.txt +2 -1
- pylinks-0.0.0.dev28/src/PyLinks.egg-info/requires.txt +3 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/__init__.py +1 -1
- pylinks-0.0.0.dev28/src/pylinks/exception/__init__.py +2 -0
- pylinks-0.0.0.dev28/src/pylinks/exception/api.py +166 -0
- pylinks-0.0.0.dev28/src/pylinks/exception/base.py +50 -0
- pylinks-0.0.0.dev28/src/pylinks/exception/media_type.py +34 -0
- pylinks-0.0.0.dev28/src/pylinks/exception/uri.py +20 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/http.py +32 -27
- pylinks-0.0.0.dev27/src/PyLinks.egg-info/requires.txt +0 -1
- pylinks-0.0.0.dev27/src/pylinks/exception/__init__.py +0 -2
- pylinks-0.0.0.dev27/src/pylinks/exception/base.py +0 -23
- pylinks-0.0.0.dev27/src/pylinks/exception/media_type.py +0 -20
- pylinks-0.0.0.dev27/src/pylinks/exception/uri.py +0 -13
- pylinks-0.0.0.dev27/src/pylinks/exceptions.py +0 -74
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/setup.cfg +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/PyLinks.egg-info/dependency_links.txt +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/PyLinks.egg-info/not-zip-safe +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/PyLinks.egg-info/top_level.txt +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/_settings.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/api/__init__.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/api/doi.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/api/github.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/api/orcid.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/media_type.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/__init__.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/binder.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/conda.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/github.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/lib_io.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/pypi.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/site/readthedocs.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/string.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/uri/__init__.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/uri/data.py +0 -0
- {pylinks-0.0.0.dev27 → pylinks-0.0.0.dev28}/src/pylinks/url.py +0 -0
|
@@ -17,10 +17,12 @@ namespaces = true
|
|
|
17
17
|
# ----------------------------------------- Project Metadata -------------------------------------
|
|
18
18
|
#
|
|
19
19
|
[project]
|
|
20
|
-
version = "0.0.0.
|
|
20
|
+
version = "0.0.0.dev28"
|
|
21
21
|
name = "PyLinks"
|
|
22
22
|
dependencies = [
|
|
23
23
|
"requests >= 2.31.0, < 3",
|
|
24
|
+
"ExceptionMan == 0.0.0.dev15",
|
|
25
|
+
"MDit == 0.0.0.dev15",
|
|
24
26
|
]
|
|
25
27
|
requires-python = ">=3.10"
|
|
26
28
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
README.md
|
|
1
2
|
pyproject.toml
|
|
2
3
|
src/PyLinks.egg-info/PKG-INFO
|
|
3
4
|
src/PyLinks.egg-info/SOURCES.txt
|
|
@@ -7,7 +8,6 @@ src/PyLinks.egg-info/requires.txt
|
|
|
7
8
|
src/PyLinks.egg-info/top_level.txt
|
|
8
9
|
src/pylinks/__init__.py
|
|
9
10
|
src/pylinks/_settings.py
|
|
10
|
-
src/pylinks/exceptions.py
|
|
11
11
|
src/pylinks/http.py
|
|
12
12
|
src/pylinks/media_type.py
|
|
13
13
|
src/pylinks/string.py
|
|
@@ -17,6 +17,7 @@ src/pylinks/api/doi.py
|
|
|
17
17
|
src/pylinks/api/github.py
|
|
18
18
|
src/pylinks/api/orcid.py
|
|
19
19
|
src/pylinks/exception/__init__.py
|
|
20
|
+
src/pylinks/exception/api.py
|
|
20
21
|
src/pylinks/exception/base.py
|
|
21
22
|
src/pylinks/exception/media_type.py
|
|
22
23
|
src/pylinks/exception/uri.py
|
|
@@ -12,4 +12,4 @@ Other available modules offer shortcuts for creating useful URLs for popular onl
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from pylinks._settings import settings
|
|
15
|
-
from pylinks import url, http, api, site,
|
|
15
|
+
from pylinks import url, http, api, site, uri, media_type, string
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Custom exceptions raised by the package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations as _annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import mdit as _mdit
|
|
8
|
+
from pylinks.exception import PyLinksError
|
|
9
|
+
|
|
10
|
+
if _TYPE_CHECKING:
|
|
11
|
+
from typing import Any, Callable
|
|
12
|
+
from requests import PreparedRequest, Request, Response
|
|
13
|
+
from requests.exceptions import RequestException
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebAPIError(PyLinksError):
|
|
17
|
+
"""Base Exception class for all web API exceptions."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WebAPIRequestError(WebAPIError):
|
|
22
|
+
def __init__(self, request_error: RequestException):
|
|
23
|
+
self.request = request_error.request
|
|
24
|
+
self.response = request_error.response
|
|
25
|
+
details = []
|
|
26
|
+
if self.request:
|
|
27
|
+
details.append(_process_request(self.request))
|
|
28
|
+
if self.response:
|
|
29
|
+
summary, response_details = _process_response(self.response)
|
|
30
|
+
details.append(response_details)
|
|
31
|
+
|
|
32
|
+
super().__init__(
|
|
33
|
+
title="Web API Request Error",
|
|
34
|
+
intro=str(request_error),
|
|
35
|
+
details=_mdit.block_container(*details) if details else None,
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WebAPIStatusCodeError(WebAPIError):
|
|
41
|
+
"""
|
|
42
|
+
Base Exception class for web API status code related exceptions.
|
|
43
|
+
By default, raised when status code is in range [400, 600).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, response: Response):
|
|
47
|
+
self.request = response.request
|
|
48
|
+
self.response = response
|
|
49
|
+
response_summary, response_details = _process_response(response)
|
|
50
|
+
details = _mdit.block_container(
|
|
51
|
+
_process_request(self.request),
|
|
52
|
+
response_details,
|
|
53
|
+
)
|
|
54
|
+
super().__init__(
|
|
55
|
+
title="Web API Status Code Error",
|
|
56
|
+
intro=response_summary,
|
|
57
|
+
details=details,
|
|
58
|
+
)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class WebAPITemporaryStatusCodeError(WebAPIStatusCodeError):
|
|
63
|
+
"""
|
|
64
|
+
Exception class for status code errors related to temporary issues.
|
|
65
|
+
By default, raised when status code is in (408, 429, 500, 502, 503, 504).
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class WebAPIPersistentStatusCodeError(WebAPIStatusCodeError):
|
|
71
|
+
"""
|
|
72
|
+
Exception class for status code errors related to persistent issues.
|
|
73
|
+
By default, raised when status code is in range [400, 600),
|
|
74
|
+
but not in (408, 429, 500, 502, 503, 504).
|
|
75
|
+
"""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class WebAPIValueError(WebAPIError):
|
|
80
|
+
"""
|
|
81
|
+
Exception class for response value errors.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, response_value: Any, response_verifier: Callable[[Any], bool]):
|
|
85
|
+
self.response_value = response_value
|
|
86
|
+
self.response_verifier = response_verifier
|
|
87
|
+
error_msg = (
|
|
88
|
+
f"Response verifier function {response_verifier} failed to verify {response_value}."
|
|
89
|
+
)
|
|
90
|
+
super().__init__(
|
|
91
|
+
title="Web API Response Verification Error",
|
|
92
|
+
intro=error_msg,
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _process_response(response: Response):
|
|
98
|
+
# Decode error reason from server
|
|
99
|
+
# This part is adapted from `requests` library; See PR #3538 on their GitHub
|
|
100
|
+
if isinstance(response.reason, bytes):
|
|
101
|
+
try:
|
|
102
|
+
reason = response.reason.decode("utf-8")
|
|
103
|
+
except UnicodeDecodeError:
|
|
104
|
+
reason = response.reason.decode("iso-8859-1")
|
|
105
|
+
else:
|
|
106
|
+
reason = response.reason
|
|
107
|
+
|
|
108
|
+
response_info = []
|
|
109
|
+
response_summary = _mdit.element.field_list()
|
|
110
|
+
side = "Client" if response.status_code < 500 else "Server"
|
|
111
|
+
for title, value in (
|
|
112
|
+
("Status Code", response.status_code),
|
|
113
|
+
("Side", side),
|
|
114
|
+
("Reason", reason),
|
|
115
|
+
("URL", response.url),
|
|
116
|
+
):
|
|
117
|
+
if value:
|
|
118
|
+
response_summary.append(
|
|
119
|
+
title=title,
|
|
120
|
+
body=_mdit.element.code_span(str(value)),
|
|
121
|
+
)
|
|
122
|
+
if response_summary.content.elements():
|
|
123
|
+
response_info.append(response_summary)
|
|
124
|
+
if response.text:
|
|
125
|
+
response_info.append(
|
|
126
|
+
_mdit.element.code_block(response.text, caption="Content")
|
|
127
|
+
)
|
|
128
|
+
summary = f"HTTP {response.status_code} error ({side.lower()} side) from {response.url}: {reason}"
|
|
129
|
+
return summary, _mdit.element.dropdown(
|
|
130
|
+
title="Response",
|
|
131
|
+
body=response_info,
|
|
132
|
+
icon="📥"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _process_request(request: Request | PreparedRequest):
|
|
137
|
+
request_info = []
|
|
138
|
+
request_summary = _mdit.element.field_list()
|
|
139
|
+
for title, attr_name in (
|
|
140
|
+
("Method", "method"),
|
|
141
|
+
("URL", "url"),
|
|
142
|
+
):
|
|
143
|
+
value = getattr(request, attr_name, None)
|
|
144
|
+
if value:
|
|
145
|
+
request_summary.append(
|
|
146
|
+
title=title,
|
|
147
|
+
body=_mdit.element.code_span(value),
|
|
148
|
+
)
|
|
149
|
+
if request_summary.content.elements():
|
|
150
|
+
request_info.append(request_summary)
|
|
151
|
+
for title, attr_name in (
|
|
152
|
+
("Data", "data"),
|
|
153
|
+
("JSON", "json"),
|
|
154
|
+
("Parameters", "params"),
|
|
155
|
+
("Body", "body"),
|
|
156
|
+
):
|
|
157
|
+
value = getattr(request, attr_name, None)
|
|
158
|
+
if value:
|
|
159
|
+
request_info.append(
|
|
160
|
+
_mdit.element.code_block(str(value), caption=title)
|
|
161
|
+
)
|
|
162
|
+
return _mdit.element.dropdown(
|
|
163
|
+
title="Request",
|
|
164
|
+
body=request_info,
|
|
165
|
+
icon="📤"
|
|
166
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""PyLinks base exception."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations as _annotations
|
|
4
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
5
|
+
from functools import partial as _partial
|
|
6
|
+
|
|
7
|
+
from exceptionman import ReporterException as _ReporterException
|
|
8
|
+
import mdit as _mdit
|
|
9
|
+
|
|
10
|
+
if _TYPE_CHECKING:
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PyLinksError(_ReporterException):
|
|
15
|
+
"""Base exception for PyLinks.
|
|
16
|
+
|
|
17
|
+
All exceptions raised by PyLinks inherit from this class.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
title: str,
|
|
22
|
+
intro,
|
|
23
|
+
details = None,
|
|
24
|
+
):
|
|
25
|
+
sphinx_config = {"html_title": "PyLinks Error Report"}
|
|
26
|
+
sphinx_target_config = _mdit.target.sphinx(
|
|
27
|
+
renderer=_partial(
|
|
28
|
+
_mdit.render.sphinx,
|
|
29
|
+
config=_mdit.render.get_sphinx_config(sphinx_config)
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
report = _mdit.document(
|
|
33
|
+
heading=title,
|
|
34
|
+
body={"intro": intro},
|
|
35
|
+
section={"details": _mdit.document(heading="Details", body=details)} if details else None,
|
|
36
|
+
target_configs_md={"sphinx": sphinx_target_config},
|
|
37
|
+
)
|
|
38
|
+
super().__init__(report=report)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PyLinksFileNotFoundError(PyLinksError):
|
|
43
|
+
"""File not found error."""
|
|
44
|
+
def __init__(self, path: Path):
|
|
45
|
+
super().__init__(
|
|
46
|
+
title="File Not Found Error",
|
|
47
|
+
intro=_mdit.inline_container("No file found at input path ", _mdit.element.code_span(str(path))),
|
|
48
|
+
)
|
|
49
|
+
self.path = path
|
|
50
|
+
return
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import mdit as _mdit
|
|
2
|
+
|
|
3
|
+
from pylinks.exception import PyLinksError as _PyLinksError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PyLinksMediaTypeParseError(_PyLinksError):
|
|
7
|
+
"""Error parsing a media type."""
|
|
8
|
+
def __init__(self, problem: str, media_type: str):
|
|
9
|
+
super().__init__(
|
|
10
|
+
title="Media Type Parse Error",
|
|
11
|
+
intro=_mdit.inline_container(
|
|
12
|
+
"Failed to parse media type ",
|
|
13
|
+
_mdit.element.code_span(media_type),
|
|
14
|
+
". ",
|
|
15
|
+
problem,
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
self.problem = problem
|
|
19
|
+
self.media_type = media_type
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PyLinksMediaTypeGuessError(_PyLinksError):
|
|
24
|
+
"""Error guessing the media type of a data URI."""
|
|
25
|
+
def __init__(self, path: str):
|
|
26
|
+
super().__init__(
|
|
27
|
+
title="Media Type Guess Error",
|
|
28
|
+
intro=_mdit.inline_container(
|
|
29
|
+
"Failed to guess the media type of the file at path ",
|
|
30
|
+
_mdit.element.code_span(path),
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
self.path = path
|
|
34
|
+
return
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import mdit as _mdit
|
|
2
|
+
|
|
3
|
+
from pylinks.exception import PyLinksError as _PyLinksError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PyLinksDataURIParseError(_PyLinksError):
|
|
7
|
+
"""Error parsing a data URI."""
|
|
8
|
+
def __init__(self, problem: str, data_uri: str):
|
|
9
|
+
super().__init__(
|
|
10
|
+
title="Data URI Parse Error",
|
|
11
|
+
intro=_mdit.inline_container(
|
|
12
|
+
"Failed to parse data URI ",
|
|
13
|
+
_mdit.element.code_span(data_uri),
|
|
14
|
+
". ",
|
|
15
|
+
problem,
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
self.problem = problem
|
|
19
|
+
self.data_uri = data_uri
|
|
20
|
+
return
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Handling HTTP requests and responses.
|
|
3
3
|
"""
|
|
4
|
+
from __future__ import annotations as _annotations
|
|
4
5
|
|
|
6
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING, NamedTuple as _NamedTuple
|
|
5
7
|
|
|
6
|
-
from typing import (
|
|
7
|
-
Any,
|
|
8
|
-
Callable,
|
|
9
|
-
List,
|
|
10
|
-
Literal,
|
|
11
|
-
NamedTuple,
|
|
12
|
-
NoReturn,
|
|
13
|
-
Optional,
|
|
14
|
-
Sequence,
|
|
15
|
-
Tuple,
|
|
16
|
-
Union,
|
|
17
|
-
Type,
|
|
18
|
-
)
|
|
19
8
|
import time
|
|
20
9
|
from functools import wraps
|
|
21
10
|
from pathlib import Path
|
|
22
11
|
import requests
|
|
23
|
-
|
|
12
|
+
|
|
13
|
+
from pylinks.exception import api as _exception
|
|
14
|
+
|
|
15
|
+
if _TYPE_CHECKING:
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Callable,
|
|
19
|
+
List,
|
|
20
|
+
Literal,
|
|
21
|
+
NoReturn,
|
|
22
|
+
Optional,
|
|
23
|
+
Sequence,
|
|
24
|
+
Tuple,
|
|
25
|
+
Union,
|
|
26
|
+
Type,
|
|
27
|
+
)
|
|
28
|
+
from pylinks.url import URL
|
|
29
|
+
|
|
24
30
|
|
|
25
31
|
|
|
26
|
-
class RetryConfig(
|
|
32
|
+
class RetryConfig(_NamedTuple):
|
|
27
33
|
"""
|
|
28
34
|
Configuration for the `retry_on_exception` decorator.
|
|
29
35
|
|
|
@@ -46,7 +52,7 @@ class RetryConfig(NamedTuple):
|
|
|
46
52
|
sleep_time_scale: float = 3
|
|
47
53
|
|
|
48
54
|
|
|
49
|
-
class HTTPRequestRetryConfig(
|
|
55
|
+
class HTTPRequestRetryConfig(_NamedTuple):
|
|
50
56
|
"""
|
|
51
57
|
Configurations for retrying an HTTP request
|
|
52
58
|
sent by `opencadd.webapi.http_request.response_http_request`.
|
|
@@ -73,7 +79,7 @@ class HTTPRequestRetryConfig(NamedTuple):
|
|
|
73
79
|
|
|
74
80
|
|
|
75
81
|
def request(
|
|
76
|
-
url: str |
|
|
82
|
+
url: str | URL,
|
|
77
83
|
verb: Union[str, Literal["GET", "POST", "PUT", "PATCH", "OPTIONS", "DELETE"]] = "GET",
|
|
78
84
|
params: Optional[Union[dict, List[tuple], bytes]] = None,
|
|
79
85
|
data: Optional[Union[dict, List[tuple], bytes]] = None,
|
|
@@ -170,7 +176,7 @@ def request(
|
|
|
170
176
|
json=json,
|
|
171
177
|
)
|
|
172
178
|
except requests.exceptions.RequestException as e:
|
|
173
|
-
raise
|
|
179
|
+
raise _exception.WebAPIRequestError(e) from e
|
|
174
180
|
_raise_for_status_code(
|
|
175
181
|
response=response,
|
|
176
182
|
temporary_error_status_codes=(
|
|
@@ -192,7 +198,7 @@ def request(
|
|
|
192
198
|
else _retry_on_exception(
|
|
193
199
|
get_response,
|
|
194
200
|
config=retry_config.config_status,
|
|
195
|
-
catch=
|
|
201
|
+
catch=_exception.WebAPITemporaryStatusCodeError,
|
|
196
202
|
)
|
|
197
203
|
)
|
|
198
204
|
# Call the (decorated or non-decorated) response function.
|
|
@@ -215,13 +221,12 @@ def request(
|
|
|
215
221
|
if response_verifier is None or response_verifier(response_value):
|
|
216
222
|
return response_value
|
|
217
223
|
# otherwise raise
|
|
218
|
-
raise
|
|
224
|
+
raise _exception.WebAPIValueError(response_value=response_value, response_verifier=response_verifier)
|
|
219
225
|
|
|
220
226
|
# Depending on specifications in argument `retry_config`, either decorate `get_response_value`
|
|
221
227
|
# with `retry_on_exception`, or leave it as is.
|
|
222
228
|
response_val_func = (
|
|
223
|
-
get_response_value
|
|
224
|
-
if (
|
|
229
|
+
get_response_value if (
|
|
225
230
|
retry_config is None
|
|
226
231
|
or retry_config.config_response is None
|
|
227
232
|
or response_verifier is None
|
|
@@ -229,7 +234,7 @@ def request(
|
|
|
229
234
|
else _retry_on_exception(
|
|
230
235
|
get_response_value,
|
|
231
236
|
config=retry_config.config_response,
|
|
232
|
-
catch=
|
|
237
|
+
catch=_exception.WebAPIValueError,
|
|
233
238
|
)
|
|
234
239
|
)
|
|
235
240
|
# Call the (decorated or non-decorated) response-value function and return.
|
|
@@ -269,9 +274,9 @@ def graphql_query(
|
|
|
269
274
|
response = request(**args)
|
|
270
275
|
if isinstance(response, dict):
|
|
271
276
|
if "errors" in response:
|
|
272
|
-
raise
|
|
277
|
+
raise _exception.WebAPIError(response)
|
|
273
278
|
elif "data" not in response:
|
|
274
|
-
raise
|
|
279
|
+
raise _exception.WebAPIError(response)
|
|
275
280
|
else:
|
|
276
281
|
response = response["data"]
|
|
277
282
|
return response
|
|
@@ -355,9 +360,9 @@ def _raise_for_status_code(
|
|
|
355
360
|
temporary_error_status_codes is not None
|
|
356
361
|
and response.status_code in temporary_error_status_codes
|
|
357
362
|
):
|
|
358
|
-
raise
|
|
363
|
+
raise _exception.WebAPITemporaryStatusCodeError(response)
|
|
359
364
|
if error_status_code_range[0] <= response.status_code <= error_status_code_range[1]:
|
|
360
|
-
raise
|
|
365
|
+
raise _exception.WebAPIPersistentStatusCodeError(response)
|
|
361
366
|
return
|
|
362
367
|
|
|
363
368
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
requests<3,>=2.31.0
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""PyLinks base exception."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path as _Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class PyLinksException(Exception):
|
|
7
|
-
"""Base exception for PyLinks.
|
|
8
|
-
|
|
9
|
-
All exceptions raised by PyLinks inherit from this class.
|
|
10
|
-
"""
|
|
11
|
-
def __init__(self, message: str):
|
|
12
|
-
self.message = message
|
|
13
|
-
super().__init__(message)
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class PyLinksFileNotFoundError(PyLinksException):
|
|
18
|
-
"""File not found error."""
|
|
19
|
-
def __init__(self, path: str | _Path):
|
|
20
|
-
msg = f"No file found at input path '{path}.'"
|
|
21
|
-
super().__init__(message=msg)
|
|
22
|
-
self.path = path
|
|
23
|
-
return
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from pylinks.exception import PyLinksException as _PyLinksException
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class PyLinksMediaTypeParseError(_PyLinksException):
|
|
5
|
-
"""Error parsing a media type."""
|
|
6
|
-
def __init__(self, message: str, media_type: str):
|
|
7
|
-
msg = f"Failed to parse media type '{media_type}': {message}"
|
|
8
|
-
super().__init__(message=msg)
|
|
9
|
-
self.message = message
|
|
10
|
-
self.media_type = media_type
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class PyLinksMediaTypeGuessError(_PyLinksException):
|
|
15
|
-
"""Error guessing the media type of a data URI."""
|
|
16
|
-
def __init__(self, path: str):
|
|
17
|
-
msg = f"Failed to guess the media type of '{path}'."
|
|
18
|
-
super().__init__(message=msg)
|
|
19
|
-
self.path = path
|
|
20
|
-
return
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from pathlib import Path as _Path
|
|
2
|
-
|
|
3
|
-
from pylinks.exception import PyLinksException as _PyLinksException
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class PyLinksDataURIParseError(_PyLinksException):
|
|
7
|
-
"""Error parsing a data URI."""
|
|
8
|
-
def __init__(self, message: str, data_uri: str):
|
|
9
|
-
msg = f"Failed to parse data URI '{data_uri}': {message}"
|
|
10
|
-
super().__init__(message=msg)
|
|
11
|
-
self.message = message
|
|
12
|
-
self.data_uri = data_uri
|
|
13
|
-
return
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"""Custom exceptions raised by the package."""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from typing import Any, Callable
|
|
5
|
-
import requests
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class WebAPIError(IOError):
|
|
9
|
-
"""Base Exception class for all web API exceptions."""
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class WebAPIStatusCodeError(WebAPIError):
|
|
14
|
-
"""
|
|
15
|
-
Base Exception class for web API status code related exceptions.
|
|
16
|
-
By default, raised when status code is in range [400, 600).
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, response: requests.Response):
|
|
20
|
-
self.response: requests.Response = response
|
|
21
|
-
# Decode error reason from server
|
|
22
|
-
# This part is adapted from `requests` library; See PR #3538 on their GitHub
|
|
23
|
-
if isinstance(response.reason, bytes):
|
|
24
|
-
try:
|
|
25
|
-
self.reason = response.reason.decode("utf-8")
|
|
26
|
-
except UnicodeDecodeError:
|
|
27
|
-
self.reason = response.reason.decode("iso-8859-1")
|
|
28
|
-
else:
|
|
29
|
-
self.reason = response.reason
|
|
30
|
-
self.response_msg = response.text
|
|
31
|
-
self.side = "client" if response.status_code < 500 else "server"
|
|
32
|
-
self.status_code = response.status_code
|
|
33
|
-
self.url = response.url
|
|
34
|
-
|
|
35
|
-
error_msg = (
|
|
36
|
-
f"HTTP {self.side} error (status code: {self.status_code})\n"
|
|
37
|
-
f"- From: {self.url}\n"
|
|
38
|
-
f"- Reason: {self.reason}\n"
|
|
39
|
-
f"- Response: {self.response_msg}"
|
|
40
|
-
)
|
|
41
|
-
super().__init__(error_msg)
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class WebAPITemporaryStatusCodeError(WebAPIStatusCodeError):
|
|
46
|
-
"""
|
|
47
|
-
Exception class for status code errors related to temporary issues.
|
|
48
|
-
By default, raised when status code is in (408, 429, 500, 502, 503, 504).
|
|
49
|
-
"""
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class WebAPIPersistentStatusCodeError(WebAPIStatusCodeError):
|
|
54
|
-
"""
|
|
55
|
-
Exception class for status code errors related to persistent issues.
|
|
56
|
-
By default, raised when status code is in range [400, 600),
|
|
57
|
-
but not in (408, 429, 500, 502, 503, 504).
|
|
58
|
-
"""
|
|
59
|
-
pass
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class WebAPIValueError(WebAPIError):
|
|
63
|
-
"""
|
|
64
|
-
Exception class for response value errors.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
def __init__(self, response_value: Any, response_verifier: Callable[[Any], bool]):
|
|
68
|
-
self.response_value = response_value
|
|
69
|
-
self.response_verifier = response_verifier
|
|
70
|
-
error_msg = (
|
|
71
|
-
f"Response verifier function {response_verifier} failed to verify {response_value}."
|
|
72
|
-
)
|
|
73
|
-
super().__init__(error_msg)
|
|
74
|
-
return
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|