omlish 0.0.0.dev468__py3-none-any.whl → 0.0.0.dev470__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/http/all.py +31 -7
 - omlish/http/clients/asyncs.py +33 -21
 - 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 +118 -25
 - omlish/http/clients/executor.py +50 -0
 - omlish/http/clients/httpx.py +35 -15
 - omlish/http/clients/middleware.py +181 -0
 - omlish/http/clients/sync.py +33 -21
 - omlish/http/clients/syncasync.py +43 -0
 - 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/inject/impl/inspect.py +7 -1
 - 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 +26 -24
 - {omlish-0.0.0.dev468.dist-info → omlish-0.0.0.dev470.dist-info}/METADATA +1 -1
 - {omlish-0.0.0.dev468.dist-info → omlish-0.0.0.dev470.dist-info}/RECORD +30 -22
 - {omlish-0.0.0.dev468.dist-info → omlish-0.0.0.dev470.dist-info}/WHEEL +0 -0
 - {omlish-0.0.0.dev468.dist-info → omlish-0.0.0.dev470.dist-info}/entry_points.txt +0 -0
 - {omlish-0.0.0.dev468.dist-info → omlish-0.0.0.dev470.dist-info}/licenses/LICENSE +0 -0
 - {omlish-0.0.0.dev468.dist-info → omlish-0.0.0.dev470.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
         
     | 
    
        omlish/http/all.py
    CHANGED
    
    | 
         @@ -7,16 +7,15 @@ with _lang.auto_proxy_init(globals()): 
     | 
|
| 
       7 
7 
     | 
    
         
             
                from .clients.asyncs import (  # noqa
         
     | 
| 
       8 
8 
     | 
    
         
             
                    AsyncStreamHttpResponse,
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                     
     | 
| 
       11 
     | 
    
         
            -
                     
     | 
| 
       12 
     | 
    
         
            -
                     
     | 
| 
      
 10 
     | 
    
         
            +
                    async_close_http_client_response,
         
     | 
| 
      
 11 
     | 
    
         
            +
                    async_closing_http_client_response,
         
     | 
| 
      
 12 
     | 
    
         
            +
                    async_read_http_client_response,
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                    AsyncHttpClient,
         
     | 
| 
       15 
15 
     | 
    
         
             
                )
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
17 
     | 
    
         
             
                from .clients.base import (  # noqa
         
     | 
| 
       18 
18 
     | 
    
         
             
                    DEFAULT_ENCODING,
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
19 
     | 
    
         
             
                    is_success_status,
         
     | 
| 
       21 
20 
     | 
    
         | 
| 
       22 
21 
     | 
    
         
             
                    HttpRequest,
         
     | 
| 
         @@ -24,32 +23,57 @@ with _lang.auto_proxy_init(globals()): 
     | 
|
| 
       24 
23 
     | 
    
         
             
                    BaseHttpResponse,
         
     | 
| 
       25 
24 
     | 
    
         
             
                    HttpResponse,
         
     | 
| 
       26 
25 
     | 
    
         | 
| 
      
 26 
     | 
    
         
            +
                    HttpClientContext,
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
       27 
28 
     | 
    
         
             
                    HttpClientError,
         
     | 
| 
       28 
29 
     | 
    
         
             
                    HttpStatusError,
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    BaseHttpClient,
         
     | 
| 
       29 
32 
     | 
    
         
             
                )
         
     | 
| 
       30 
33 
     | 
    
         | 
| 
       31 
34 
     | 
    
         
             
                from .clients.default import (  # noqa
         
     | 
| 
       32 
35 
     | 
    
         
             
                    client,
         
     | 
| 
      
 36 
     | 
    
         
            +
                    manage_client,
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
       33 
38 
     | 
    
         
             
                    request,
         
     | 
| 
       34 
39 
     | 
    
         | 
| 
       35 
40 
     | 
    
         
             
                    async_client,
         
     | 
| 
      
 41 
     | 
    
         
            +
                    manage_async_client,
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
       36 
43 
     | 
    
         
             
                    async_request,
         
     | 
| 
       37 
44 
     | 
    
         
             
                )
         
     | 
| 
       38 
45 
     | 
    
         | 
| 
       39 
46 
     | 
    
         
             
                from .clients.httpx import (  # noqa
         
     | 
| 
       40 
47 
     | 
    
         
             
                    HttpxHttpClient,
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    HttpxAsyncHttpClient,
         
     | 
| 
      
 50 
     | 
    
         
            +
                )
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                from .clients.middleware import (  # noqa
         
     | 
| 
      
 53 
     | 
    
         
            +
                    HttpClientMiddleware,
         
     | 
| 
      
 54 
     | 
    
         
            +
                    AbstractMiddlewareHttpClient,
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    MiddlewareHttpClient,
         
     | 
| 
      
 57 
     | 
    
         
            +
                    MiddlewareAsyncHttpClient,
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    TooManyRedirectsHttpClientError,
         
     | 
| 
      
 60 
     | 
    
         
            +
                    RedirectHandlingHttpClientMiddleware,
         
     | 
| 
       41 
61 
     | 
    
         
             
                )
         
     | 
| 
       42 
62 
     | 
    
         | 
| 
       43 
63 
     | 
    
         
             
                from .clients.sync import (  # noqa
         
     | 
| 
       44 
64 
     | 
    
         
             
                    StreamHttpResponse,
         
     | 
| 
       45 
65 
     | 
    
         | 
| 
       46 
     | 
    
         
            -
                     
     | 
| 
       47 
     | 
    
         
            -
                     
     | 
| 
       48 
     | 
    
         
            -
                     
     | 
| 
      
 66 
     | 
    
         
            +
                    close_http_client_response,
         
     | 
| 
      
 67 
     | 
    
         
            +
                    closing_http_client_response,
         
     | 
| 
      
 68 
     | 
    
         
            +
                    read_http_client_response,
         
     | 
| 
       49 
69 
     | 
    
         | 
| 
       50 
70 
     | 
    
         
             
                    HttpClient,
         
     | 
| 
       51 
71 
     | 
    
         
             
                )
         
     | 
| 
       52 
72 
     | 
    
         | 
| 
      
 73 
     | 
    
         
            +
                from .clients.syncasync import (  # noqa
         
     | 
| 
      
 74 
     | 
    
         
            +
                    SyncAsyncHttpClient,
         
     | 
| 
      
 75 
     | 
    
         
            +
                )
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
       53 
77 
     | 
    
         
             
                from .clients.urllib import (  # noqa
         
     | 
| 
       54 
78 
     | 
    
         
             
                    UrllibHttpClient,
         
     | 
| 
       55 
79 
     | 
    
         
             
                )
         
     | 
    
        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,13 +57,13 @@ 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()  # noqa
         
     | 
| 
       54 
61 
     | 
    
         | 
| 
       55 
62 
     | 
    
         | 
| 
       56 
63 
     | 
    
         
             
            #
         
     | 
| 
       57 
64 
     | 
    
         | 
| 
       58 
65 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
            async def  
     | 
| 
      
 66 
     | 
    
         
            +
            async def async_close_http_client_response(resp: BaseHttpResponse) -> None:
         
     | 
| 
       60 
67 
     | 
    
         
             
                if isinstance(resp, HttpResponse):
         
     | 
| 
       61 
68 
     | 
    
         
             
                    pass
         
     | 
| 
       62 
69 
     | 
    
         | 
| 
         @@ -68,7 +75,7 @@ async def async_close_response(resp: BaseHttpResponse) -> None: 
     | 
|
| 
       68 
75 
     | 
    
         | 
| 
       69 
76 
     | 
    
         | 
| 
       70 
77 
     | 
    
         
             
            @contextlib.asynccontextmanager
         
     | 
| 
       71 
     | 
    
         
            -
            async def  
     | 
| 
      
 78 
     | 
    
         
            +
            async def async_closing_http_client_response(resp: BaseHttpResponseT) -> ta.AsyncGenerator[BaseHttpResponseT, None]:
         
     | 
| 
       72 
79 
     | 
    
         
             
                if isinstance(resp, HttpResponse):
         
     | 
| 
       73 
80 
     | 
    
         
             
                    yield resp
         
     | 
| 
       74 
81 
     | 
    
         
             
                    return
         
     | 
| 
         @@ -83,15 +90,14 @@ async def async_closing_response(resp: BaseHttpResponseT) -> ta.AsyncGenerator[B 
     | 
|
| 
       83 
90 
     | 
    
         
             
                    raise TypeError(resp)
         
     | 
| 
       84 
91 
     | 
    
         | 
| 
       85 
92 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
            async def  
     | 
| 
      
 93 
     | 
    
         
            +
            async def async_read_http_client_response(resp: BaseHttpResponse) -> HttpResponse:
         
     | 
| 
       87 
94 
     | 
    
         
             
                if isinstance(resp, 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,21 +118,27 @@ 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 
     | 
    
         
            -
                    async with  
     | 
| 
      
 124 
     | 
    
         
            +
                    async with async_closing_http_client_response(await self.stream_request(
         
     | 
| 
       118 
125 
     | 
    
         
             
                            req,
         
     | 
| 
      
 126 
     | 
    
         
            +
                            context=context,
         
     | 
| 
       119 
127 
     | 
    
         
             
                            check=check,
         
     | 
| 
       120 
128 
     | 
    
         
             
                    )) as resp:
         
     | 
| 
       121 
     | 
    
         
            -
                        return await  
     | 
| 
      
 129 
     | 
    
         
            +
                        return await async_read_http_client_response(resp)
         
     | 
| 
       122 
130 
     | 
    
         | 
| 
       123 
131 
     | 
    
         
             
                async def stream_request(
         
     | 
| 
       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:
         
     | 
| 
         @@ -134,14 +146,14 @@ class AsyncHttpClient(Abstract): 
     | 
|
| 
       134 
146 
     | 
    
         
             
                                cause = resp.underlying
         
     | 
| 
       135 
147 
     | 
    
         
             
                            else:
         
     | 
| 
       136 
148 
     | 
    
         
             
                                cause = None
         
     | 
| 
       137 
     | 
    
         
            -
                            raise HttpStatusError(await  
     | 
| 
      
 149 
     | 
    
         
            +
                            raise HttpStatusError(await async_read_http_client_response(resp)) from cause  # noqa
         
     | 
| 
       138 
150 
     | 
    
         | 
| 
       139 
151 
     | 
    
         
             
                    except Exception:
         
     | 
| 
       140 
     | 
    
         
            -
                        await  
     | 
| 
      
 152 
     | 
    
         
            +
                        await async_close_http_client_response(resp)
         
     | 
| 
       141 
153 
     | 
    
         
             
                        raise
         
     | 
| 
       142 
154 
     | 
    
         | 
| 
       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()
         
     |