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()
         
     |