omlish 0.0.0.dev467__py3-none-any.whl → 0.0.0.dev469__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.
- omlish/__about__.py +2 -2
- omlish/asyncs/asyncio/sync.py +43 -0
- omlish/asyncs/sync.py +25 -0
- omlish/dataclasses/impl/api/classes/decorator.py +3 -0
- omlish/dataclasses/impl/api/classes/make.py +3 -0
- omlish/dataclasses/impl/concerns/repr.py +15 -2
- omlish/dataclasses/specs.py +1 -0
- omlish/http/all.py +16 -0
- omlish/http/clients/asyncs.py +26 -14
- omlish/http/clients/base.py +17 -1
- omlish/http/clients/coro/__init__.py +0 -0
- omlish/http/clients/coro/sync.py +170 -0
- omlish/http/clients/default.py +208 -29
- omlish/http/clients/executor.py +50 -0
- omlish/http/clients/httpx.py +82 -4
- omlish/http/clients/middleware.py +178 -0
- omlish/http/clients/sync.py +25 -13
- omlish/http/clients/urllib.py +4 -2
- omlish/http/coro/client/connection.py +15 -6
- omlish/http/coro/io.py +2 -0
- omlish/http/urls.py +67 -0
- omlish/io/buffers.py +3 -0
- omlish/lang/__init__.py +3 -0
- omlish/lang/functions.py +9 -4
- omlish/lang/params.py +17 -0
- omlish/sync.py +62 -21
- {omlish-0.0.0.dev467.dist-info → omlish-0.0.0.dev469.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev467.dist-info → omlish-0.0.0.dev469.dist-info}/RECORD +32 -25
- {omlish-0.0.0.dev467.dist-info → omlish-0.0.0.dev469.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev467.dist-info → omlish-0.0.0.dev469.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev467.dist-info → omlish-0.0.0.dev469.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev467.dist-info → omlish-0.0.0.dev469.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# ruff: noqa: UP045
|
|
2
|
+
# @omlish-lite
|
|
3
|
+
import asyncio
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from ...sync import SyncBufferRelay
|
|
7
|
+
from ..sync import AsyncBufferRelay
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
T = ta.TypeVar('T')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@ta.final
|
|
17
|
+
class AsyncioBufferRelay(AsyncBufferRelay[T]):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
event: ta.Optional[asyncio.Event] = None,
|
|
22
|
+
loop: ta.Optional[ta.Any] = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
if event is None:
|
|
25
|
+
event = asyncio.Event()
|
|
26
|
+
self._event = event
|
|
27
|
+
if loop is None:
|
|
28
|
+
loop = asyncio.get_running_loop()
|
|
29
|
+
self._loop = loop
|
|
30
|
+
|
|
31
|
+
self._relay: SyncBufferRelay[T] = SyncBufferRelay(
|
|
32
|
+
wake_fn=lambda: loop.call_soon_threadsafe(event.set), # type: ignore[arg-type]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def push(self, *vs: T) -> None:
|
|
36
|
+
self._relay.push(*vs)
|
|
37
|
+
|
|
38
|
+
def swap(self) -> ta.Sequence[T]:
|
|
39
|
+
return self._relay.swap()
|
|
40
|
+
|
|
41
|
+
async def wait(self) -> None:
|
|
42
|
+
await self._event.wait()
|
|
43
|
+
self._event.clear()
|
omlish/asyncs/sync.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @omlish-lite
|
|
2
|
+
import abc
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from ..lite.abstract import Abstract
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
T = ta.TypeVar('T')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AsyncBufferRelay(Abstract, ta.Generic[T]):
|
|
15
|
+
@abc.abstractmethod
|
|
16
|
+
def push(self, *vs: T) -> None:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
@abc.abstractmethod
|
|
20
|
+
def swap(self) -> ta.Sequence[T]:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
@abc.abstractmethod
|
|
24
|
+
async def wait(self) -> None:
|
|
25
|
+
raise NotImplementedError
|
|
@@ -10,6 +10,7 @@ from ..... import lang
|
|
|
10
10
|
from ...._internals import STD_FIELDS_ATTR
|
|
11
11
|
from ...._internals import STD_PARAMS_ATTR
|
|
12
12
|
from ....specs import ClassSpec
|
|
13
|
+
from ....specs import ReprFn
|
|
13
14
|
from ...processing.driving import drive_cls_processing
|
|
14
15
|
from ...utils import class_decorator
|
|
15
16
|
from ..fields.building import build_cls_std_fields
|
|
@@ -53,6 +54,7 @@ def dataclass(
|
|
|
53
54
|
|
|
54
55
|
repr_id: bool | None = None,
|
|
55
56
|
terse_repr: bool | None = None,
|
|
57
|
+
default_repr_fn: ReprFn | None = None,
|
|
56
58
|
|
|
57
59
|
allow_redundant_decorator: bool | None = None,
|
|
58
60
|
|
|
@@ -158,6 +160,7 @@ def dataclass(
|
|
|
158
160
|
|
|
159
161
|
repr_id=repr_id,
|
|
160
162
|
terse_repr=terse_repr,
|
|
163
|
+
default_repr_fn=default_repr_fn,
|
|
161
164
|
|
|
162
165
|
allow_redundant_decorator=allow_redundant_decorator,
|
|
163
166
|
),
|
|
@@ -4,6 +4,7 @@ import sys
|
|
|
4
4
|
import types
|
|
5
5
|
import typing as ta
|
|
6
6
|
|
|
7
|
+
from ....specs import ReprFn
|
|
7
8
|
from .decorator import dataclass
|
|
8
9
|
|
|
9
10
|
|
|
@@ -53,6 +54,7 @@ def make_dataclass( # noqa
|
|
|
53
54
|
|
|
54
55
|
repr_id: bool | None = None,
|
|
55
56
|
terse_repr: bool | None = None,
|
|
57
|
+
default_repr_fn: ReprFn | None = None,
|
|
56
58
|
|
|
57
59
|
allow_redundant_decorator: bool | None = None,
|
|
58
60
|
|
|
@@ -174,6 +176,7 @@ def make_dataclass( # noqa
|
|
|
174
176
|
|
|
175
177
|
repr_id=repr_id,
|
|
176
178
|
terse_repr=terse_repr,
|
|
179
|
+
default_repr_fn=default_repr_fn,
|
|
177
180
|
|
|
178
181
|
allow_redundant_decorator=allow_redundant_decorator,
|
|
179
182
|
)
|
|
@@ -32,6 +32,7 @@ class ReprPlan(Plan):
|
|
|
32
32
|
|
|
33
33
|
id: bool = False
|
|
34
34
|
terse: bool = False
|
|
35
|
+
default_fn: OpRef[ReprFn] | None = None
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
@register_generator_type(ReprPlan)
|
|
@@ -66,11 +67,17 @@ class ReprGenerator(Generator[ReprPlan]):
|
|
|
66
67
|
fn=fnr,
|
|
67
68
|
))
|
|
68
69
|
|
|
70
|
+
drf: OpRef | None = None
|
|
71
|
+
if ctx.cs.default_repr_fn is not None:
|
|
72
|
+
drf = OpRef(f'repr.default_fn')
|
|
73
|
+
orm[drf] = ctx.cs.default_repr_fn
|
|
74
|
+
|
|
69
75
|
return PlanResult(
|
|
70
76
|
ReprPlan(
|
|
71
77
|
fields=tuple(rfs),
|
|
72
78
|
id=ctx.cs.repr_id,
|
|
73
79
|
terse=ctx.cs.terse_repr,
|
|
80
|
+
default_fn=drf,
|
|
74
81
|
),
|
|
75
82
|
orm,
|
|
76
83
|
)
|
|
@@ -85,10 +92,16 @@ class ReprGenerator(Generator[ReprPlan]):
|
|
|
85
92
|
if not (pl.terse and not f.kw_only):
|
|
86
93
|
pfx = f'{f.name}='
|
|
87
94
|
|
|
95
|
+
fn: OpRef[ReprFn] | None = None
|
|
88
96
|
if f.fn is not None:
|
|
89
|
-
|
|
97
|
+
fn = f.fn
|
|
98
|
+
elif pl.default_fn is not None:
|
|
99
|
+
fn = pl.default_fn
|
|
100
|
+
|
|
101
|
+
if fn is not None:
|
|
102
|
+
ors.add(fn)
|
|
90
103
|
part_lines.extend([
|
|
91
|
-
f' if (s := {
|
|
104
|
+
f' if (s := {fn.ident()}(self.{f.name})) is not None:',
|
|
92
105
|
f' parts.append(f"{pfx}{{s}}")',
|
|
93
106
|
])
|
|
94
107
|
else:
|
omlish/dataclasses/specs.py
CHANGED
omlish/http/all.py
CHANGED
|
@@ -4,6 +4,16 @@ from .. import lang as _lang
|
|
|
4
4
|
with _lang.auto_proxy_init(globals()):
|
|
5
5
|
##
|
|
6
6
|
|
|
7
|
+
from .clients.asyncs import ( # noqa
|
|
8
|
+
AsyncStreamHttpResponse,
|
|
9
|
+
|
|
10
|
+
async_close_response,
|
|
11
|
+
async_closing_response,
|
|
12
|
+
async_read_response,
|
|
13
|
+
|
|
14
|
+
AsyncHttpClient,
|
|
15
|
+
)
|
|
16
|
+
|
|
7
17
|
from .clients.base import ( # noqa
|
|
8
18
|
DEFAULT_ENCODING,
|
|
9
19
|
|
|
@@ -20,8 +30,14 @@ with _lang.auto_proxy_init(globals()):
|
|
|
20
30
|
|
|
21
31
|
from .clients.default import ( # noqa
|
|
22
32
|
client,
|
|
33
|
+
manage_client,
|
|
23
34
|
|
|
24
35
|
request,
|
|
36
|
+
|
|
37
|
+
async_client,
|
|
38
|
+
manage_async_client,
|
|
39
|
+
|
|
40
|
+
async_request,
|
|
25
41
|
)
|
|
26
42
|
|
|
27
43
|
from .clients.httpx import ( # noqa
|
omlish/http/clients/asyncs.py
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
import abc
|
|
4
4
|
import contextlib
|
|
5
5
|
import dataclasses as dc
|
|
6
|
+
import io
|
|
6
7
|
import typing as ta
|
|
7
8
|
|
|
8
9
|
from ...lite.abstract import Abstract
|
|
9
|
-
from ...lite.dataclasses import dataclass_maybe_post_init
|
|
10
10
|
from ...lite.dataclasses import dataclass_shallow_asdict
|
|
11
|
+
from .base import BaseHttpClient
|
|
11
12
|
from .base import BaseHttpResponse
|
|
12
13
|
from .base import BaseHttpResponseT
|
|
14
|
+
from .base import HttpClientContext
|
|
13
15
|
from .base import HttpRequest
|
|
14
16
|
from .base import HttpResponse
|
|
15
17
|
from .base import HttpStatusError
|
|
@@ -26,21 +28,26 @@ AsyncHttpClientT = ta.TypeVar('AsyncHttpClientT', bound='AsyncHttpClient')
|
|
|
26
28
|
@dc.dataclass(frozen=True) # kw_only=True
|
|
27
29
|
class AsyncStreamHttpResponse(BaseHttpResponse):
|
|
28
30
|
class Stream(ta.Protocol):
|
|
29
|
-
def
|
|
31
|
+
def read1(self, /, n: int = -1) -> ta.Awaitable[bytes]: ...
|
|
30
32
|
|
|
31
33
|
@ta.final
|
|
32
34
|
class _NullStream:
|
|
33
|
-
def
|
|
35
|
+
def read1(self, /, n: int = -1) -> ta.Awaitable[bytes]:
|
|
34
36
|
raise TypeError
|
|
35
37
|
|
|
36
38
|
stream: Stream = _NullStream()
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
@property
|
|
41
|
+
def has_data(self) -> bool:
|
|
42
|
+
return not isinstance(self.stream, AsyncStreamHttpResponse._NullStream)
|
|
43
|
+
|
|
44
|
+
async def read_all(self) -> bytes:
|
|
45
|
+
buf = io.BytesIO()
|
|
46
|
+
while (b := await self.stream.read1()):
|
|
47
|
+
buf.write(b)
|
|
48
|
+
return buf.getvalue()
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
dataclass_maybe_post_init(super())
|
|
42
|
-
if isinstance(self.stream, AsyncStreamHttpResponse._NullStream):
|
|
43
|
-
raise TypeError(self.stream)
|
|
50
|
+
_closer: ta.Optional[ta.Callable[[], ta.Awaitable[None]]] = None
|
|
44
51
|
|
|
45
52
|
async def __aenter__(self: AsyncStreamHttpResponseT) -> AsyncStreamHttpResponseT:
|
|
46
53
|
return self
|
|
@@ -50,7 +57,7 @@ class AsyncStreamHttpResponse(BaseHttpResponse):
|
|
|
50
57
|
|
|
51
58
|
async def close(self) -> None:
|
|
52
59
|
if (c := self._closer) is not None:
|
|
53
|
-
c()
|
|
60
|
+
await c()
|
|
54
61
|
|
|
55
62
|
|
|
56
63
|
#
|
|
@@ -88,10 +95,9 @@ async def async_read_response(resp: BaseHttpResponse) -> HttpResponse:
|
|
|
88
95
|
return resp
|
|
89
96
|
|
|
90
97
|
elif isinstance(resp, AsyncStreamHttpResponse):
|
|
91
|
-
data = await resp.stream.read()
|
|
92
98
|
return HttpResponse(**{
|
|
93
99
|
**{k: v for k, v in dataclass_shallow_asdict(resp).items() if k not in ('stream', '_closer')},
|
|
94
|
-
'data':
|
|
100
|
+
**({'data': await resp.read_all()} if resp.has_data else {}),
|
|
95
101
|
})
|
|
96
102
|
|
|
97
103
|
else:
|
|
@@ -101,7 +107,7 @@ async def async_read_response(resp: BaseHttpResponse) -> HttpResponse:
|
|
|
101
107
|
##
|
|
102
108
|
|
|
103
109
|
|
|
104
|
-
class AsyncHttpClient(Abstract):
|
|
110
|
+
class AsyncHttpClient(BaseHttpClient, Abstract):
|
|
105
111
|
async def __aenter__(self: AsyncHttpClientT) -> AsyncHttpClientT:
|
|
106
112
|
return self
|
|
107
113
|
|
|
@@ -112,10 +118,12 @@ class AsyncHttpClient(Abstract):
|
|
|
112
118
|
self,
|
|
113
119
|
req: HttpRequest,
|
|
114
120
|
*,
|
|
121
|
+
context: ta.Optional[HttpClientContext] = None,
|
|
115
122
|
check: bool = False,
|
|
116
123
|
) -> HttpResponse:
|
|
117
124
|
async with async_closing_response(await self.stream_request(
|
|
118
125
|
req,
|
|
126
|
+
context=context,
|
|
119
127
|
check=check,
|
|
120
128
|
)) as resp:
|
|
121
129
|
return await async_read_response(resp)
|
|
@@ -124,9 +132,13 @@ class AsyncHttpClient(Abstract):
|
|
|
124
132
|
self,
|
|
125
133
|
req: HttpRequest,
|
|
126
134
|
*,
|
|
135
|
+
context: ta.Optional[HttpClientContext] = None,
|
|
127
136
|
check: bool = False,
|
|
128
137
|
) -> AsyncStreamHttpResponse:
|
|
129
|
-
|
|
138
|
+
if context is None:
|
|
139
|
+
context = HttpClientContext()
|
|
140
|
+
|
|
141
|
+
resp = await self._stream_request(context, req)
|
|
130
142
|
|
|
131
143
|
try:
|
|
132
144
|
if check and not resp.is_success:
|
|
@@ -143,5 +155,5 @@ class AsyncHttpClient(Abstract):
|
|
|
143
155
|
return resp
|
|
144
156
|
|
|
145
157
|
@abc.abstractmethod
|
|
146
|
-
def _stream_request(self, req: HttpRequest) -> ta.Awaitable[AsyncStreamHttpResponse]:
|
|
158
|
+
def _stream_request(self, ctx: HttpClientContext, req: HttpRequest) -> ta.Awaitable[AsyncStreamHttpResponse]:
|
|
147
159
|
raise NotImplementedError
|
omlish/http/clients/base.py
CHANGED
|
@@ -119,12 +119,28 @@ class HttpResponse(BaseHttpResponse):
|
|
|
119
119
|
##
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
@ta.final
|
|
123
|
+
class HttpClientContext:
|
|
124
|
+
def __init__(self) -> None:
|
|
125
|
+
self._dct: dict = {}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
|
|
130
|
+
|
|
122
131
|
class HttpClientError(Exception):
|
|
123
132
|
@property
|
|
124
133
|
def cause(self) -> ta.Optional[BaseException]:
|
|
125
134
|
return self.__cause__
|
|
126
135
|
|
|
127
136
|
|
|
128
|
-
@dc.dataclass(
|
|
137
|
+
@dc.dataclass()
|
|
129
138
|
class HttpStatusError(HttpClientError):
|
|
130
139
|
response: HttpResponse
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class BaseHttpClient(Abstract):
|
|
146
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @omlish-lite
|
|
2
|
+
# ruff: noqa: UP045
|
|
3
|
+
import errno
|
|
4
|
+
import socket
|
|
5
|
+
import typing as ta
|
|
6
|
+
import urllib.parse
|
|
7
|
+
|
|
8
|
+
from ....lite.check import check
|
|
9
|
+
from ...coro.client.connection import CoroHttpClientConnection
|
|
10
|
+
from ...coro.client.response import CoroHttpClientResponse
|
|
11
|
+
from ...coro.io import CoroHttpIo
|
|
12
|
+
from ...headers import HttpHeaders
|
|
13
|
+
from ...urls import unparse_url_request_path
|
|
14
|
+
from ..base import HttpClientContext
|
|
15
|
+
from ..base import HttpClientError
|
|
16
|
+
from ..base import HttpRequest
|
|
17
|
+
from ..sync import HttpClient
|
|
18
|
+
from ..sync import StreamHttpResponse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
T = ta.TypeVar('T')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CoroHttpClient(HttpClient):
|
|
28
|
+
class _Connection:
|
|
29
|
+
def __init__(self, req: HttpRequest) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
self._req = req
|
|
33
|
+
self._ups = urllib.parse.urlparse(req.url)
|
|
34
|
+
|
|
35
|
+
self._ssl = self._ups.scheme == 'https'
|
|
36
|
+
|
|
37
|
+
_cc: ta.Optional[CoroHttpClientConnection] = None
|
|
38
|
+
_resp: ta.Optional[CoroHttpClientResponse] = None
|
|
39
|
+
|
|
40
|
+
_sock: ta.Optional[socket.socket] = None
|
|
41
|
+
_sock_file: ta.Optional[ta.BinaryIO] = None
|
|
42
|
+
|
|
43
|
+
_ssl_context: ta.Any = None
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
|
|
47
|
+
def _create_https_context(self, http_version: int) -> ta.Any:
|
|
48
|
+
# https://github.com/python/cpython/blob/a7160912274003672dc116d918260c0a81551c21/Lib/http/client.py#L809
|
|
49
|
+
import ssl
|
|
50
|
+
|
|
51
|
+
# Function also used by urllib.request to be able to set the check_hostname attribute on a context object.
|
|
52
|
+
context = ssl.create_default_context()
|
|
53
|
+
|
|
54
|
+
# Send ALPN extension to indicate HTTP/1.1 protocol.
|
|
55
|
+
if http_version == 11:
|
|
56
|
+
context.set_alpn_protocols(['http/1.1'])
|
|
57
|
+
|
|
58
|
+
# Enable PHA for TLS 1.3 connections if available.
|
|
59
|
+
if context.post_handshake_auth is not None:
|
|
60
|
+
context.post_handshake_auth = True
|
|
61
|
+
|
|
62
|
+
return context
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
|
|
66
|
+
def setup(self) -> StreamHttpResponse:
|
|
67
|
+
check.none(self._sock)
|
|
68
|
+
check.none(self._ssl_context)
|
|
69
|
+
|
|
70
|
+
self._cc = cc = CoroHttpClientConnection(
|
|
71
|
+
check.not_none(self._ups.hostname),
|
|
72
|
+
default_port=CoroHttpClientConnection.HTTPS_PORT if self._ssl else CoroHttpClientConnection.HTTP_PORT,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if self._ssl:
|
|
76
|
+
self._ssl_context = self._create_https_context(self._cc.http_version)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
self._run_coro(cc.connect())
|
|
80
|
+
|
|
81
|
+
self._run_coro(cc.request(
|
|
82
|
+
self._req.method or 'GET',
|
|
83
|
+
unparse_url_request_path(self._ups) or '/',
|
|
84
|
+
self._req.data,
|
|
85
|
+
hh.single_str_dct if (hh := self._req.headers_) is not None else {},
|
|
86
|
+
))
|
|
87
|
+
|
|
88
|
+
self._resp = resp = self._run_coro(cc.get_response())
|
|
89
|
+
|
|
90
|
+
return StreamHttpResponse(
|
|
91
|
+
status=resp._state.status, # noqa
|
|
92
|
+
headers=HttpHeaders(resp._state.headers.items()), # noqa
|
|
93
|
+
request=self._req,
|
|
94
|
+
underlying=self,
|
|
95
|
+
stream=self,
|
|
96
|
+
_closer=self.close,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
except Exception:
|
|
100
|
+
self.close()
|
|
101
|
+
raise
|
|
102
|
+
|
|
103
|
+
def _run_coro(self, g: ta.Generator[ta.Any, ta.Any, T]) -> T:
|
|
104
|
+
i = None
|
|
105
|
+
|
|
106
|
+
while True:
|
|
107
|
+
try:
|
|
108
|
+
o = g.send(i)
|
|
109
|
+
except StopIteration as e:
|
|
110
|
+
return e.value
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
i = self._handle_io(o)
|
|
114
|
+
except OSError as e:
|
|
115
|
+
raise HttpClientError from e
|
|
116
|
+
|
|
117
|
+
def _handle_io(self, o: CoroHttpIo.Io) -> ta.Any:
|
|
118
|
+
if isinstance(o, CoroHttpIo.ConnectIo):
|
|
119
|
+
check.none(self._sock)
|
|
120
|
+
self._sock = socket.create_connection(*o.args, **(o.kwargs or {}))
|
|
121
|
+
|
|
122
|
+
if self._ssl_context is not None:
|
|
123
|
+
self._sock = self._ssl_context.wrap_socket(
|
|
124
|
+
self._sock,
|
|
125
|
+
server_hostname=check.not_none(o.server_hostname),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Might fail in OSs that don't implement TCP_NODELAY
|
|
129
|
+
try:
|
|
130
|
+
self._sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
131
|
+
except OSError as e:
|
|
132
|
+
if e.errno != errno.ENOPROTOOPT:
|
|
133
|
+
raise
|
|
134
|
+
|
|
135
|
+
self._sock_file = self._sock.makefile('rb')
|
|
136
|
+
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
elif isinstance(o, CoroHttpIo.CloseIo):
|
|
140
|
+
check.not_none(self._sock).close()
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
elif isinstance(o, CoroHttpIo.WriteIo):
|
|
144
|
+
check.not_none(self._sock).sendall(o.data)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
elif isinstance(o, CoroHttpIo.ReadIo):
|
|
148
|
+
if (sz := o.sz) is not None:
|
|
149
|
+
return check.not_none(self._sock_file).read(sz)
|
|
150
|
+
else:
|
|
151
|
+
return check.not_none(self._sock_file).read()
|
|
152
|
+
|
|
153
|
+
elif isinstance(o, CoroHttpIo.ReadLineIo):
|
|
154
|
+
return check.not_none(self._sock_file).readline(o.sz)
|
|
155
|
+
|
|
156
|
+
else:
|
|
157
|
+
raise TypeError(o)
|
|
158
|
+
|
|
159
|
+
def read1(self, /, n: int = -1) -> bytes:
|
|
160
|
+
return self._run_coro(check.not_none(self._resp).read(n if n >= 0 else None))
|
|
161
|
+
|
|
162
|
+
def close(self) -> None:
|
|
163
|
+
if self._resp is not None:
|
|
164
|
+
self._resp.close()
|
|
165
|
+
if self._sock is not None:
|
|
166
|
+
self._sock.close()
|
|
167
|
+
|
|
168
|
+
def _stream_request(self, ctx: HttpClientContext, req: HttpRequest) -> StreamHttpResponse:
|
|
169
|
+
conn = CoroHttpClient._Connection(req)
|
|
170
|
+
return conn.setup()
|