mm-std 0.3.25__py3-none-any.whl → 0.3.26__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.
- mm_std/__init__.py +3 -0
- mm_std/http/__init__.py +0 -0
- mm_std/http/http_request.py +121 -0
- mm_std/http/response.py +93 -0
- {mm_std-0.3.25.dist-info → mm_std-0.3.26.dist-info}/METADATA +1 -1
- {mm_std-0.3.25.dist-info → mm_std-0.3.26.dist-info}/RECORD +7 -4
- {mm_std-0.3.25.dist-info → mm_std-0.3.26.dist-info}/WHEEL +0 -0
mm_std/__init__.py
CHANGED
@@ -19,6 +19,9 @@ from .date import utc_now as utc_now
|
|
19
19
|
from .date import utc_random as utc_random
|
20
20
|
from .dict import replace_empty_dict_values as replace_empty_dict_values
|
21
21
|
from .env import get_dotenv as get_dotenv
|
22
|
+
from .http.http_request import http_request as http_request
|
23
|
+
from .http.response import HttpError as HttpError
|
24
|
+
from .http.response import HttpResponse as HttpResponse
|
22
25
|
from .http_ import CHROME_USER_AGENT as CHROME_USER_AGENT
|
23
26
|
from .http_ import FIREFOX_USER_AGENT as FIREFOX_USER_AGENT
|
24
27
|
from .http_ import HResponse as HResponse
|
mm_std/http/__init__.py
ADDED
File without changes
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import aiohttp
|
2
|
+
from aiohttp.typedefs import LooseCookies, Query
|
3
|
+
from aiohttp_socks import ProxyConnector
|
4
|
+
from multidict import CIMultiDictProxy
|
5
|
+
|
6
|
+
from mm_std.http.response import HttpError, HttpResponse
|
7
|
+
|
8
|
+
|
9
|
+
async def http_request(
|
10
|
+
url: str,
|
11
|
+
*,
|
12
|
+
method: str = "GET",
|
13
|
+
params: Query | None = None,
|
14
|
+
data: dict[str, object] | None = None,
|
15
|
+
json: dict[str, object] | None = None,
|
16
|
+
headers: dict[str, str] | None = None,
|
17
|
+
cookies: LooseCookies | None = None,
|
18
|
+
user_agent: str | None = None,
|
19
|
+
proxy: str | None = None,
|
20
|
+
timeout: float | None = 10.0,
|
21
|
+
) -> HttpResponse:
|
22
|
+
"""
|
23
|
+
Send an HTTP request and return the response.
|
24
|
+
"""
|
25
|
+
timeout_ = aiohttp.ClientTimeout(total=timeout) if timeout else None
|
26
|
+
if user_agent:
|
27
|
+
if not headers:
|
28
|
+
headers = {}
|
29
|
+
headers["user-agent"] = user_agent
|
30
|
+
|
31
|
+
try:
|
32
|
+
if proxy and proxy.startswith("socks"):
|
33
|
+
return await _request_with_socks_proxy(
|
34
|
+
url,
|
35
|
+
method=method,
|
36
|
+
params=params,
|
37
|
+
data=data,
|
38
|
+
json=json,
|
39
|
+
headers=headers,
|
40
|
+
cookies=cookies,
|
41
|
+
proxy=proxy,
|
42
|
+
timeout=timeout_,
|
43
|
+
)
|
44
|
+
return await _request_with_http_or_none_proxy(
|
45
|
+
url,
|
46
|
+
method=method,
|
47
|
+
params=params,
|
48
|
+
data=data,
|
49
|
+
json=json,
|
50
|
+
headers=headers,
|
51
|
+
cookies=cookies,
|
52
|
+
proxy=proxy,
|
53
|
+
timeout=timeout_,
|
54
|
+
)
|
55
|
+
except TimeoutError as err:
|
56
|
+
return HttpResponse(error=HttpError.TIMEOUT, error_message=str(err))
|
57
|
+
except Exception as err:
|
58
|
+
return HttpResponse(error=HttpError.ERROR, error_message=str(err))
|
59
|
+
|
60
|
+
|
61
|
+
async def _request_with_http_or_none_proxy(
|
62
|
+
url: str,
|
63
|
+
*,
|
64
|
+
method: str = "GET",
|
65
|
+
params: Query | None = None,
|
66
|
+
data: dict[str, object] | None = None,
|
67
|
+
json: dict[str, object] | None = None,
|
68
|
+
headers: dict[str, str] | None = None,
|
69
|
+
cookies: LooseCookies | None = None,
|
70
|
+
proxy: str | None = None,
|
71
|
+
timeout: aiohttp.ClientTimeout | None,
|
72
|
+
) -> HttpResponse:
|
73
|
+
async with aiohttp.request(
|
74
|
+
method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, proxy=proxy, timeout=timeout
|
75
|
+
) as res:
|
76
|
+
return HttpResponse(
|
77
|
+
status=res.status,
|
78
|
+
error=None,
|
79
|
+
error_message=None,
|
80
|
+
body=(await res.read()).decode(),
|
81
|
+
headers=headers_dict(res.headers),
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
async def _request_with_socks_proxy(
|
86
|
+
url: str,
|
87
|
+
*,
|
88
|
+
method: str = "GET",
|
89
|
+
proxy: str,
|
90
|
+
params: Query | None = None,
|
91
|
+
data: dict[str, object] | None = None,
|
92
|
+
json: dict[str, object] | None = None,
|
93
|
+
headers: dict[str, str] | None = None,
|
94
|
+
cookies: LooseCookies | None = None,
|
95
|
+
timeout: aiohttp.ClientTimeout | None,
|
96
|
+
) -> HttpResponse:
|
97
|
+
connector = ProxyConnector.from_url(proxy)
|
98
|
+
async with (
|
99
|
+
aiohttp.ClientSession(connector=connector) as session,
|
100
|
+
session.request(
|
101
|
+
method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, timeout=timeout
|
102
|
+
) as res,
|
103
|
+
):
|
104
|
+
return HttpResponse(
|
105
|
+
status=res.status,
|
106
|
+
error=None,
|
107
|
+
error_message=None,
|
108
|
+
body=(await res.read()).decode(),
|
109
|
+
headers=headers_dict(res.headers),
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
def headers_dict(headers: CIMultiDictProxy[str]) -> dict[str, str]:
|
114
|
+
result: dict[str, str] = {}
|
115
|
+
for key in headers:
|
116
|
+
values = headers.getall(key)
|
117
|
+
if len(values) == 1:
|
118
|
+
result[key] = values[0]
|
119
|
+
else:
|
120
|
+
result[key] = ", ".join(values)
|
121
|
+
return result
|
mm_std/http/response.py
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
import enum
|
2
|
+
import json
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
import pydash
|
6
|
+
from pydantic import GetCoreSchemaHandler
|
7
|
+
from pydantic_core import CoreSchema, core_schema
|
8
|
+
|
9
|
+
|
10
|
+
@enum.unique
|
11
|
+
class HttpError(str, enum.Enum):
|
12
|
+
TIMEOUT = "timeout"
|
13
|
+
PROXY = "proxy"
|
14
|
+
CONNECTION = "connection"
|
15
|
+
ERROR = "error"
|
16
|
+
|
17
|
+
|
18
|
+
class HttpResponse:
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
status: int | None = None,
|
22
|
+
error: HttpError | None = None,
|
23
|
+
error_message: str | None = None,
|
24
|
+
body: str | None = None,
|
25
|
+
headers: dict[str, str] | None = None,
|
26
|
+
) -> None:
|
27
|
+
self.status = status
|
28
|
+
self.error = error
|
29
|
+
self.error_message = error_message
|
30
|
+
self.body = body
|
31
|
+
self.headers = headers
|
32
|
+
|
33
|
+
self._json_data: Any = None
|
34
|
+
self._json_parsed = False
|
35
|
+
self._json_parsed_error = False
|
36
|
+
|
37
|
+
def _parse_json(self) -> None:
|
38
|
+
if self.body is None:
|
39
|
+
self._json_parsed_error = True
|
40
|
+
return
|
41
|
+
try:
|
42
|
+
self._json_data = None
|
43
|
+
self._json_data = json.loads(self.body)
|
44
|
+
self._json_parsed_error = False
|
45
|
+
except json.JSONDecodeError:
|
46
|
+
self._json_parsed_error = True
|
47
|
+
self._json_parsed = True
|
48
|
+
|
49
|
+
def json(self, path: str | None = None) -> Any: # noqa: ANN401
|
50
|
+
if not self._json_parsed:
|
51
|
+
self._parse_json()
|
52
|
+
if path:
|
53
|
+
return pydash.get(self._json_data, path, None)
|
54
|
+
return self._json_data
|
55
|
+
|
56
|
+
def dict(self) -> dict[str, object]:
|
57
|
+
return {
|
58
|
+
"status": self.status,
|
59
|
+
"error": self.error,
|
60
|
+
"error_message": self.error_message,
|
61
|
+
"body": self.body,
|
62
|
+
"headers": self.headers,
|
63
|
+
}
|
64
|
+
|
65
|
+
def is_json_parse_error(self) -> bool:
|
66
|
+
if not self._json_parsed:
|
67
|
+
self._parse_json()
|
68
|
+
return self._json_parsed_error
|
69
|
+
|
70
|
+
def __repr__(self) -> str:
|
71
|
+
return f"HttpResponse(status={self.status}, error={self.error}, error_message={self.error_message}, body={self.body}, headers={self.headers})" # noqa: E501
|
72
|
+
|
73
|
+
@classmethod
|
74
|
+
def __get_pydantic_core_schema__(cls, source_type: type[Any], handler: GetCoreSchemaHandler) -> CoreSchema:
|
75
|
+
return core_schema.no_info_after_validator_function(
|
76
|
+
cls._validate,
|
77
|
+
core_schema.any_schema(),
|
78
|
+
serialization=core_schema.plain_serializer_function_ser_schema(lambda x: x.dict()),
|
79
|
+
)
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def _validate(cls, v: object) -> "HttpResponse":
|
83
|
+
if isinstance(v, cls):
|
84
|
+
return v
|
85
|
+
if isinstance(v, dict):
|
86
|
+
return cls(
|
87
|
+
status=v.get("status"),
|
88
|
+
error=HttpError(v["error"]) if v.get("error") else None,
|
89
|
+
error_message=v.get("error_message"),
|
90
|
+
body=v.get("body"),
|
91
|
+
headers=v.get("headers"),
|
92
|
+
)
|
93
|
+
raise TypeError(f"Cannot parse value as {cls.__name__}: {v}")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
mm_std/__init__.py,sha256=
|
1
|
+
mm_std/__init__.py,sha256=Q0UguFT4b_LdKmxFSI4NQ47mwYRVRTjZ6fgYh8qZY4Q,3266
|
2
2
|
mm_std/command.py,sha256=ze286wjUjg0QSTgIu-2WZks53_Vclg69UaYYgPpQvCU,1283
|
3
3
|
mm_std/config.py,sha256=4ox4D2CgGR76bvZ2n2vGQOYUDagFnlKEDb87to5zpxE,1871
|
4
4
|
mm_std/crypto.py,sha256=jdk0_TCmeU0pPXMyz9xH6kQHSjjZ9GcGClBwQps5vBo,340
|
@@ -25,6 +25,9 @@ mm_std/concurrency/async_task_runner.py,sha256=EN7tN2enkVYVgDbhSiAr-_W4o9m9wBXCv
|
|
25
25
|
mm_std/concurrency/sync_decorators.py,sha256=syCQBOmN7qPO55yzgJB2rbkh10CVww376hmyvs6e5tA,1080
|
26
26
|
mm_std/concurrency/sync_scheduler.py,sha256=j4tBL_cBI1spr0cZplTA7N2CoYsznuORMeRN8rpR6gY,2407
|
27
27
|
mm_std/concurrency/sync_task_runner.py,sha256=s5JPlLYLGQGHIxy4oDS-PN7O9gcy-yPZFoNm8RQwzcw,1780
|
28
|
-
mm_std
|
29
|
-
mm_std
|
30
|
-
mm_std
|
28
|
+
mm_std/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
|
+
mm_std/http/http_request.py,sha256=mJak332gv9NvY0JLO3hNrBxroQGS1HlNBzd1yq4Dy24,3694
|
30
|
+
mm_std/http/response.py,sha256=vvv5COTjJula9t33mFyrruhrFC4dr_Uy0jDKj6t1JxM,2923
|
31
|
+
mm_std-0.3.26.dist-info/METADATA,sha256=GW6fYKKEoBHo1GlukZ2LtY3gII8feRqjIynazLiX16g,415
|
32
|
+
mm_std-0.3.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
33
|
+
mm_std-0.3.26.dist-info/RECORD,,
|
File without changes
|