potato-util 0.0.1__py3-none-any.whl
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.
- potato_util/__init__.py +6 -0
- potato_util/__version__.py +1 -0
- potato_util/_base.py +117 -0
- potato_util/constants/__init__.py +5 -0
- potato_util/constants/_base.py +5 -0
- potato_util/constants/_enum.py +31 -0
- potato_util/constants/_regex.py +23 -0
- potato_util/dt.py +240 -0
- potato_util/http/__init__.py +12 -0
- potato_util/http/_async.py +42 -0
- potato_util/http/_base.py +46 -0
- potato_util/http/_sync.py +45 -0
- potato_util/http/fastapi.py +26 -0
- potato_util/io/__init__.py +11 -0
- potato_util/io/_async.py +274 -0
- potato_util/io/_sync.py +264 -0
- potato_util/sanitizer.py +85 -0
- potato_util/secure.py +90 -0
- potato_util/validator.py +150 -0
- potato_util-0.0.1.dist-info/METADATA +287 -0
- potato_util-0.0.1.dist-info/RECORD +24 -0
- potato_util-0.0.1.dist-info/WHEEL +5 -0
- potato_util-0.0.1.dist-info/licenses/LICENSE.txt +21 -0
- potato_util-0.0.1.dist-info/top_level.txt +1 -0
potato_util/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
potato_util/_base.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import copy
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from pydantic import validate_call
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@validate_call
|
|
12
|
+
def deep_merge(dict1: dict, dict2: dict) -> dict:
|
|
13
|
+
"""Return a new dictionary that's the result of a deep merge of two dictionaries.
|
|
14
|
+
If there are conflicts, values from `dict2` will overwrite those in `dict1`.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
dict1 (dict, required): The base dictionary that will be merged.
|
|
18
|
+
dict2 (dict, required): The dictionary to merge into `dict1`.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
dict: The merged dictionary.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_merged = copy.deepcopy(dict1)
|
|
25
|
+
for _key, _val in dict2.items():
|
|
26
|
+
if (
|
|
27
|
+
_key in _merged
|
|
28
|
+
and isinstance(_merged[_key], dict)
|
|
29
|
+
and isinstance(_val, dict)
|
|
30
|
+
):
|
|
31
|
+
_merged[_key] = deep_merge(_merged[_key], _val)
|
|
32
|
+
else:
|
|
33
|
+
_merged[_key] = copy.deepcopy(_val)
|
|
34
|
+
|
|
35
|
+
return _merged
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@validate_call
|
|
39
|
+
def camel_to_snake(val: str) -> str:
|
|
40
|
+
"""Convert CamelCase to snake_case.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
val (str): CamelCase string to convert.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: Converted snake_case string.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
val = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", val)
|
|
50
|
+
val = re.sub("([a-z0-9])([A-Z])", r"\1_\2", val).lower()
|
|
51
|
+
return val
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@validate_call
|
|
55
|
+
def clean_obj_dict(obj_dict: dict, cls_name: str) -> dict:
|
|
56
|
+
"""Clean class name from object.__dict__ for str(object).
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
obj_dict (dict, required): Object dictionary by object.__dict__.
|
|
60
|
+
cls_name (str , required): Class name by cls.__name__.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
dict: Clean object dictionary.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
if not obj_dict:
|
|
68
|
+
raise ValueError("'obj_dict' argument value is empty!")
|
|
69
|
+
|
|
70
|
+
if not cls_name:
|
|
71
|
+
raise ValueError("'cls_name' argument value is empty!")
|
|
72
|
+
except ValueError as err:
|
|
73
|
+
logger.error(err)
|
|
74
|
+
raise
|
|
75
|
+
|
|
76
|
+
_self_dict = obj_dict.copy()
|
|
77
|
+
for _key in _self_dict.copy():
|
|
78
|
+
_class_prefix = f"_{cls_name}__"
|
|
79
|
+
if _key.startswith(_class_prefix):
|
|
80
|
+
_new_key = _key.replace(_class_prefix, "")
|
|
81
|
+
_self_dict[_new_key] = _self_dict.pop(_key)
|
|
82
|
+
return _self_dict
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@validate_call(config={"arbitrary_types_allowed": True})
|
|
86
|
+
def obj_to_repr(obj: object) -> str:
|
|
87
|
+
"""Modifying object default repr() to custom info.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
obj (object, required): Any python object.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
str: String for repr() method.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
if not obj:
|
|
98
|
+
raise ValueError("'obj' argument value is empty!")
|
|
99
|
+
except ValueError as err:
|
|
100
|
+
logger.error(err)
|
|
101
|
+
raise
|
|
102
|
+
|
|
103
|
+
_self_repr = (
|
|
104
|
+
f"<{obj.__class__.__module__}.{obj.__class__.__name__} object at {hex(id(obj))}: "
|
|
105
|
+
+ "{"
|
|
106
|
+
+ f"{str(dir(obj)).replace('[', '').replace(']', '')}"
|
|
107
|
+
+ "}>"
|
|
108
|
+
)
|
|
109
|
+
return _self_repr
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = [
|
|
113
|
+
"deep_merge",
|
|
114
|
+
"camel_to_snake",
|
|
115
|
+
"clean_obj_dict",
|
|
116
|
+
"obj_to_repr",
|
|
117
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WarnEnum(str, Enum):
|
|
5
|
+
ERROR = "ERROR"
|
|
6
|
+
ALWAYS = "ALWAYS"
|
|
7
|
+
DEBUG = "DEBUG"
|
|
8
|
+
IGNORE = "IGNORE"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TSUnitEnum(str, Enum):
|
|
12
|
+
SECONDS = "SECONDS"
|
|
13
|
+
MILLISECONDS = "MILLISECONDS"
|
|
14
|
+
MICROSECONDS = "MICROSECONDS"
|
|
15
|
+
NANOSECONDS = "NANOSECONDS"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HashAlgoEnum(str, Enum):
|
|
19
|
+
md5 = "md5"
|
|
20
|
+
sha1 = "sha1"
|
|
21
|
+
sha224 = "sha224"
|
|
22
|
+
sha256 = "sha256"
|
|
23
|
+
sha384 = "sha384"
|
|
24
|
+
sha512 = "sha512"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"WarnEnum",
|
|
29
|
+
"TSUnitEnum",
|
|
30
|
+
"HashAlgoEnum",
|
|
31
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
REQUEST_ID_REGEX = (
|
|
2
|
+
r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b|"
|
|
3
|
+
r"\b[0-9a-fA-F]{32}\b"
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
# Invalid characters:
|
|
7
|
+
SPECIAL_CHARS_REGEX = r"[&'\"<>]"
|
|
8
|
+
SPECIAL_CHARS_BASE_REGEX = r"[&'\"<>\\\/]"
|
|
9
|
+
SPECIAL_CHARS_LOW_REGEX = r"[&'\"<>\\\/`{}|]"
|
|
10
|
+
SPECIAL_CHARS_MEDIUM_REGEX = r"[&'\"<>\\\/`{}|()\[\]]"
|
|
11
|
+
SPECIAL_CHARS_HIGH_REGEX = r"[&'\"<>\\\/`{}|()\[\]!@#$%^*;:?]"
|
|
12
|
+
SPECIAL_CHARS_STRICT_REGEX = r"[&'\"<>\\\/`{}|()\[\]~!@#$%^*_=\-+;:,.?\t\n ]"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"REQUEST_ID_REGEX",
|
|
17
|
+
"SPECIAL_CHARS_REGEX",
|
|
18
|
+
"SPECIAL_CHARS_BASE_REGEX",
|
|
19
|
+
"SPECIAL_CHARS_LOW_REGEX",
|
|
20
|
+
"SPECIAL_CHARS_MEDIUM_REGEX",
|
|
21
|
+
"SPECIAL_CHARS_HIGH_REGEX",
|
|
22
|
+
"SPECIAL_CHARS_STRICT_REGEX",
|
|
23
|
+
]
|
potato_util/dt.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
from zoneinfo import ZoneInfo
|
|
4
|
+
from datetime import datetime, timezone, tzinfo, timedelta
|
|
5
|
+
|
|
6
|
+
from pydantic import validate_call
|
|
7
|
+
|
|
8
|
+
from .constants import WarnEnum, TSUnitEnum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@validate_call(config={"arbitrary_types_allowed": True})
|
|
15
|
+
def add_tzinfo(dt: datetime, tz: ZoneInfo | tzinfo | str) -> datetime:
|
|
16
|
+
"""Add or replace timezone info to datetime object.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
dt (datetime , required): Datetime object.
|
|
20
|
+
tz (Union[ZoneInfo, tzinfo, str], required): Timezone info.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
datetime: Datetime object with timezone info.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if isinstance(tz, str):
|
|
27
|
+
tz = ZoneInfo(tz)
|
|
28
|
+
|
|
29
|
+
dt = dt.replace(tzinfo=tz)
|
|
30
|
+
return dt
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@validate_call
|
|
34
|
+
def datetime_to_iso(
|
|
35
|
+
dt: datetime, sep: str = "T", warn_mode: WarnEnum = WarnEnum.IGNORE
|
|
36
|
+
) -> str:
|
|
37
|
+
"""Convert datetime object to ISO 8601 format.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
dt (datetime, required): Datetime object.
|
|
41
|
+
sep (str , optional): Separator between date and time. Defaults to "T".
|
|
42
|
+
warn_mode (WarnEnum, optional): Warning mode. Defaults to WarnEnum.IGNORE.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If `sep` argument length is greater than 8.
|
|
46
|
+
ValueError: If `dt` argument doesn't have any timezone info and `warn_mode` is set to WarnEnum.ERROR.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: Datetime string in ISO 8601 format.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
sep = sep.strip()
|
|
53
|
+
if 8 < len(sep):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"`sep` argument length '{len(sep)}' is too long, must be less than or equal to 8!"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if not dt.tzinfo:
|
|
59
|
+
_message = "Not found any timezone info in `dt` argument, assuming it's UTC timezone..."
|
|
60
|
+
if warn_mode == WarnEnum.ALWAYS:
|
|
61
|
+
logger.warning(_message)
|
|
62
|
+
elif warn_mode == WarnEnum.DEBUG:
|
|
63
|
+
logger.debug(_message)
|
|
64
|
+
elif warn_mode == WarnEnum.ERROR:
|
|
65
|
+
_message = "Not found any timezone info in `dt` argument!"
|
|
66
|
+
logger.error(_message)
|
|
67
|
+
raise ValueError(_message)
|
|
68
|
+
|
|
69
|
+
dt = add_tzinfo(dt=dt, tz="UTC")
|
|
70
|
+
|
|
71
|
+
_dt_str = dt.isoformat(sep=sep, timespec="milliseconds")
|
|
72
|
+
return _dt_str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@validate_call(config={"arbitrary_types_allowed": True})
|
|
76
|
+
def convert_tz(
|
|
77
|
+
dt: datetime, tz: ZoneInfo | tzinfo | str, warn_mode: WarnEnum = WarnEnum.ALWAYS
|
|
78
|
+
) -> datetime:
|
|
79
|
+
"""Convert datetime object to another timezone.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
dt (datetime , required): Datetime object to convert.
|
|
83
|
+
tz (Union[ZoneInfo, tzinfo, str], required): Timezone info to convert.
|
|
84
|
+
warn_mode (WarnEnum , optional): Warning mode. Defaults to WarnEnum.ALWAYS.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: If `dt` argument doesn't have any timezone info and `warn_mode` is set to WarnEnum.ERROR.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
datetime: Datetime object which has been converted to another timezone.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if not dt.tzinfo:
|
|
94
|
+
_message = "Not found any timezone info in `dt` argument, assuming it's UTC timezone..."
|
|
95
|
+
if warn_mode == WarnEnum.ALWAYS:
|
|
96
|
+
logger.warning(_message)
|
|
97
|
+
elif warn_mode == WarnEnum.DEBUG:
|
|
98
|
+
logger.debug(_message)
|
|
99
|
+
elif warn_mode == WarnEnum.ERROR:
|
|
100
|
+
_message = "Not found any timezone info in `dt` argument!"
|
|
101
|
+
logger.error(_message)
|
|
102
|
+
raise ValueError(_message)
|
|
103
|
+
|
|
104
|
+
dt = add_tzinfo(dt=dt, tz="UTC")
|
|
105
|
+
|
|
106
|
+
if isinstance(tz, str):
|
|
107
|
+
tz = ZoneInfo(tz)
|
|
108
|
+
|
|
109
|
+
dt = dt.astimezone(tz=tz)
|
|
110
|
+
return dt
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def now_utc_dt() -> datetime:
|
|
114
|
+
"""Get current datetime in UTC timezone with tzinfo.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
datetime: Current datetime in UTC timezone with tzinfo.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
_utc_dt = datetime.now(tz=timezone.utc)
|
|
121
|
+
return _utc_dt
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def now_local_dt() -> datetime:
|
|
125
|
+
"""Get current datetime in local timezone with tzinfo.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
datetime: Current datetime in local timezone with tzinfo.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
_local_dt = datetime.now().astimezone()
|
|
132
|
+
return _local_dt
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@validate_call(config={"arbitrary_types_allowed": True})
|
|
136
|
+
def now_dt(tz: ZoneInfo | tzinfo | str) -> datetime:
|
|
137
|
+
"""Get current datetime in specified timezone with tzinfo.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
tz (Union[ZoneInfo, tzinfo, str], required): Timezone info.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
datetime: Current datetime in specified timezone with tzinfo.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
_dt = now_utc_dt()
|
|
147
|
+
_dt = convert_tz(dt=_dt, tz=tz)
|
|
148
|
+
return _dt
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@validate_call
|
|
152
|
+
def now_ts(unit: TSUnitEnum = TSUnitEnum.SECONDS) -> int:
|
|
153
|
+
"""Get current timestamp in UTC timezone.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
unit (TSUnitEnum, optional): Type of timestamp unit. Defaults to `TSUnitEnum.SECONDS`.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
int: Current timestamp.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
_now_ts: int
|
|
163
|
+
if unit == TSUnitEnum.SECONDS:
|
|
164
|
+
_now_ts = int(time.time())
|
|
165
|
+
elif unit == TSUnitEnum.MILLISECONDS:
|
|
166
|
+
_now_ts = int(time.time() * 1000)
|
|
167
|
+
elif unit == TSUnitEnum.MICROSECONDS:
|
|
168
|
+
_now_ts = int(time.time_ns() / 1000)
|
|
169
|
+
elif unit == TSUnitEnum.NANOSECONDS:
|
|
170
|
+
_now_ts = int(time.time_ns())
|
|
171
|
+
|
|
172
|
+
return _now_ts
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@validate_call
|
|
176
|
+
def convert_ts(dt: datetime, unit: TSUnitEnum = TSUnitEnum.SECONDS) -> int:
|
|
177
|
+
"""Convert datetime to timestamp.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
dt (datetime , required): Datetime object to convert.
|
|
181
|
+
unit (TSUnitEnum, optional): Type of timestamp unit. Defaults to `TSUnitEnum.SECONDS`.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
int: Converted timestamp.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
_ts: int
|
|
188
|
+
if unit == TSUnitEnum.SECONDS:
|
|
189
|
+
_ts = int(dt.timestamp())
|
|
190
|
+
elif unit == TSUnitEnum.MILLISECONDS:
|
|
191
|
+
_ts = int(dt.timestamp() * 1000)
|
|
192
|
+
elif unit == TSUnitEnum.MICROSECONDS:
|
|
193
|
+
_ts = int(dt.timestamp() * 1000000)
|
|
194
|
+
elif unit == TSUnitEnum.NANOSECONDS:
|
|
195
|
+
_ts = int(dt.timestamp() * 1000000000)
|
|
196
|
+
|
|
197
|
+
return _ts
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@validate_call(config={"arbitrary_types_allowed": True})
|
|
201
|
+
def calc_future_dt(
|
|
202
|
+
delta: timedelta | int,
|
|
203
|
+
dt: datetime | None = None,
|
|
204
|
+
tz: ZoneInfo | tzinfo | str | None = None,
|
|
205
|
+
) -> datetime:
|
|
206
|
+
"""Calculate future datetime by adding delta time to current or specified datetime.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
delta (Union[timedelta, int] , required): Delta time to add to current or specified datetime.
|
|
210
|
+
dt (Optional[datetime] , optional): Datetime before adding delta time. Defaults to None.
|
|
211
|
+
tz (Union[ZoneInfo, tzinfo, str, None], optional): Timezone info. Defaults to None.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
datetime: Calculated future datetime.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
if not dt:
|
|
218
|
+
dt = now_utc_dt()
|
|
219
|
+
|
|
220
|
+
if tz:
|
|
221
|
+
dt = convert_tz(dt=dt, tz=tz)
|
|
222
|
+
|
|
223
|
+
if isinstance(delta, int):
|
|
224
|
+
delta = timedelta(seconds=delta)
|
|
225
|
+
|
|
226
|
+
_future_dt = dt + delta
|
|
227
|
+
return _future_dt
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__all__ = [
|
|
231
|
+
"add_tzinfo",
|
|
232
|
+
"datetime_to_iso",
|
|
233
|
+
"convert_tz",
|
|
234
|
+
"now_utc_dt",
|
|
235
|
+
"now_local_dt",
|
|
236
|
+
"now_dt",
|
|
237
|
+
"now_ts",
|
|
238
|
+
"convert_ts",
|
|
239
|
+
"calc_future_dt",
|
|
240
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
from pydantic import validate_call, AnyHttpUrl
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@validate_call
|
|
6
|
+
async def async_is_connectable(
|
|
7
|
+
url: AnyHttpUrl = AnyHttpUrl("https://www.google.com"),
|
|
8
|
+
timeout: int = 3,
|
|
9
|
+
check_status: bool = False,
|
|
10
|
+
) -> bool:
|
|
11
|
+
"""Check if the url is connectable.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
url (AnyHttpUrl, optional): URL to check. Defaults to 'https://www.google.com'.
|
|
15
|
+
timeout (int , optional): Timeout in seconds. Defaults to 3.
|
|
16
|
+
check_status (bool , optional): Check HTTP status code (200). Defaults to False.
|
|
17
|
+
|
|
18
|
+
Raise:
|
|
19
|
+
ValueError: If `timeout` is less than 1.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
bool: True if connectable, False otherwise.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
if timeout < 1:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"`timeout` argument value {timeout} is invalid, must be greater than 0!"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
async with aiohttp.ClientSession() as _session:
|
|
32
|
+
async with _session.get(str(url), timeout=timeout) as _response:
|
|
33
|
+
if check_status:
|
|
34
|
+
return _response.status == 200
|
|
35
|
+
return True
|
|
36
|
+
except Exception:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"async_is_connectable",
|
|
42
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from http import HTTPStatus
|
|
2
|
+
|
|
3
|
+
from pydantic import validate_call
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@validate_call
|
|
7
|
+
def get_http_status(status_code: int) -> tuple[HTTPStatus, bool]:
|
|
8
|
+
"""Get HTTP status code enum from integer value.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
status_code (int, required): Status code for HTTP response: [100 <= status_code <= 599].
|
|
12
|
+
|
|
13
|
+
Raises:
|
|
14
|
+
ValueError: If status code is not in range [100 <= status_code <= 599].
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Tuple[HTTPStatus, bool]: Tuple of HTTP status code enum and boolean value if status code is known.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_http_status: HTTPStatus
|
|
21
|
+
_is_known_status = False
|
|
22
|
+
try:
|
|
23
|
+
_http_status = HTTPStatus(status_code)
|
|
24
|
+
_is_known_status = True
|
|
25
|
+
except ValueError:
|
|
26
|
+
if (100 <= status_code) and (status_code < 200):
|
|
27
|
+
status_code = 100
|
|
28
|
+
elif (200 <= status_code) and (status_code < 300):
|
|
29
|
+
status_code = 200
|
|
30
|
+
elif (300 <= status_code) and (status_code < 400):
|
|
31
|
+
status_code = 304
|
|
32
|
+
elif (400 <= status_code) and (status_code < 500):
|
|
33
|
+
status_code = 400
|
|
34
|
+
elif (500 <= status_code) and (status_code < 600):
|
|
35
|
+
status_code = 500
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(f"Invalid HTTP status code: '{status_code}'!")
|
|
38
|
+
|
|
39
|
+
_http_status = HTTPStatus(status_code)
|
|
40
|
+
|
|
41
|
+
return (_http_status, _is_known_status)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"get_http_status",
|
|
46
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from urllib import request
|
|
2
|
+
from http.client import HTTPResponse
|
|
3
|
+
|
|
4
|
+
from pydantic import validate_call, AnyHttpUrl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@validate_call
|
|
8
|
+
def is_connectable(
|
|
9
|
+
url: AnyHttpUrl = AnyHttpUrl("https://www.google.com"),
|
|
10
|
+
timeout: int = 3,
|
|
11
|
+
check_status: bool = False,
|
|
12
|
+
) -> bool:
|
|
13
|
+
"""Check if the url is connectable.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
url (AnyHttpUrl, optional): URL to check. Defaults to 'https://www.google.com'.
|
|
17
|
+
timeout (int , optional): Timeout in seconds. Defaults to 3.
|
|
18
|
+
check_status (bool , optional): Check HTTP status code (200). Defaults to False.
|
|
19
|
+
|
|
20
|
+
Raise:
|
|
21
|
+
ValueError: If `timeout` is less than 1.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
bool: True if connectable, False otherwise.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
if timeout < 1:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"`timeout` argument value {timeout} is invalid, must be greater than 0!"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
_response: HTTPResponse = request.urlopen(
|
|
34
|
+
str(url), timeout=timeout
|
|
35
|
+
) # nosec B310
|
|
36
|
+
if check_status:
|
|
37
|
+
return _response.getcode() == 200
|
|
38
|
+
return True
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"is_connectable",
|
|
45
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import validate_call
|
|
2
|
+
from starlette.datastructures import URL
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@validate_call(config={"arbitrary_types_allowed": True})
|
|
7
|
+
def get_relative_url(val: Request | URL) -> str:
|
|
8
|
+
"""Get relative url only path with query params from request object or URL object.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
val (Union[Request, URL]): Request object or URL object to extract relative url.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
str: Relative url only path with query params.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
if isinstance(val, Request):
|
|
18
|
+
val = val.url
|
|
19
|
+
|
|
20
|
+
_relative_url = str(val).replace(f"{val.scheme}://{val.netloc}", "")
|
|
21
|
+
return _relative_url
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"get_relative_url",
|
|
26
|
+
]
|