omlish 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev484__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/.omlish-manifests.json +12 -0
- omlish/__about__.py +15 -15
- omlish/argparse/all.py +17 -9
- omlish/argparse/cli.py +16 -3
- omlish/argparse/utils.py +21 -0
- omlish/asyncs/asyncio/rlock.py +110 -0
- omlish/asyncs/asyncio/sync.py +43 -0
- omlish/asyncs/asyncio/utils.py +2 -0
- omlish/asyncs/sync.py +25 -0
- omlish/bootstrap/_marshal.py +1 -1
- omlish/bootstrap/diag.py +12 -21
- omlish/bootstrap/main.py +2 -5
- omlish/bootstrap/sys.py +27 -28
- omlish/cexts/__init__.py +0 -0
- omlish/cexts/include/omlish/omlish.hh +1 -0
- omlish/collections/__init__.py +13 -1
- omlish/collections/attrregistry.py +210 -0
- omlish/collections/cache/impl.py +1 -0
- omlish/collections/identity.py +1 -0
- omlish/collections/mappings.py +28 -0
- omlish/collections/trie.py +5 -1
- omlish/collections/utils.py +77 -0
- omlish/concurrent/all.py +2 -1
- omlish/concurrent/futures.py +25 -0
- omlish/concurrent/threadlets.py +1 -1
- omlish/daemons/reparent.py +2 -3
- omlish/daemons/spawning.py +2 -3
- omlish/dataclasses/__init__.py +2 -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/impl/configs.py +97 -37
- omlish/dataclasses/impl/generation/compilation.py +21 -19
- omlish/dataclasses/impl/generation/globals.py +1 -0
- omlish/dataclasses/impl/generation/ops.py +1 -0
- omlish/dataclasses/impl/generation/processor.py +105 -24
- omlish/dataclasses/impl/processing/base.py +8 -0
- omlish/dataclasses/impl/processing/driving.py +8 -8
- omlish/dataclasses/specs.py +1 -0
- omlish/dataclasses/tools/modifiers.py +5 -0
- omlish/diag/cmds/__init__.py +0 -0
- omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
- omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
- omlish/diag/{ps.py → cmds/ps.py} +6 -6
- omlish/diag/pycharm.py +16 -2
- omlish/diag/pydevd.py +58 -40
- omlish/diag/replserver/console.py +1 -1
- omlish/dispatch/__init__.py +18 -12
- omlish/dispatch/methods.py +50 -140
- omlish/dom/rendering.py +1 -1
- omlish/formats/dotenv.py +1 -1
- omlish/formats/json/stream/__init__.py +13 -0
- omlish/funcs/guard.py +225 -0
- omlish/graphs/dot/rendering.py +1 -1
- omlish/http/all.py +44 -4
- omlish/http/clients/asyncs.py +33 -27
- omlish/http/clients/base.py +17 -1
- omlish/http/clients/coro/__init__.py +0 -0
- omlish/http/clients/coro/sync.py +171 -0
- omlish/http/clients/default.py +208 -29
- omlish/http/clients/executor.py +56 -0
- omlish/http/clients/httpx.py +72 -11
- omlish/http/clients/middleware.py +181 -0
- omlish/http/clients/sync.py +33 -27
- omlish/http/clients/syncasync.py +49 -0
- omlish/http/clients/urllib.py +6 -3
- omlish/http/coro/client/connection.py +15 -6
- omlish/http/coro/io.py +2 -0
- omlish/http/flasky/__init__.py +40 -0
- omlish/http/flasky/_compat.py +2 -0
- omlish/http/flasky/api.py +82 -0
- omlish/http/flasky/app.py +203 -0
- omlish/http/flasky/cvs.py +59 -0
- omlish/http/flasky/requests.py +20 -0
- omlish/http/flasky/responses.py +23 -0
- omlish/http/flasky/routes.py +23 -0
- omlish/http/flasky/types.py +15 -0
- omlish/http/urls.py +67 -0
- omlish/inject/__init__.py +38 -18
- omlish/inject/_dataclasses.py +4986 -0
- omlish/inject/binder.py +4 -48
- omlish/inject/elements.py +27 -0
- omlish/inject/helpers/__init__.py +0 -0
- omlish/inject/{utils.py → helpers/constfn.py} +3 -3
- omlish/inject/{tags.py → helpers/id.py} +2 -2
- omlish/inject/helpers/multis.py +143 -0
- omlish/inject/helpers/wrappers.py +54 -0
- omlish/inject/impl/elements.py +47 -17
- omlish/inject/impl/injector.py +20 -19
- omlish/inject/impl/inspect.py +10 -1
- omlish/inject/impl/maysync.py +3 -4
- omlish/inject/impl/multis.py +3 -0
- omlish/inject/impl/sync.py +3 -4
- omlish/inject/injector.py +31 -2
- omlish/inject/inspect.py +35 -0
- omlish/inject/maysync.py +2 -4
- omlish/inject/multis.py +8 -0
- omlish/inject/overrides.py +3 -3
- omlish/inject/privates.py +6 -0
- omlish/inject/providers.py +3 -2
- omlish/inject/sync.py +5 -4
- omlish/io/buffers.py +118 -0
- omlish/io/readers.py +29 -0
- omlish/iterators/transforms.py +2 -2
- omlish/lang/__init__.py +178 -97
- omlish/lang/_asyncs.cc +186 -0
- omlish/lang/asyncs.py +17 -0
- omlish/lang/casing.py +11 -0
- omlish/lang/contextmanagers.py +28 -4
- omlish/lang/functions.py +31 -22
- omlish/lang/imports/_capture.cc +11 -9
- omlish/lang/imports/capture.py +363 -170
- omlish/lang/imports/proxy.py +455 -152
- omlish/lang/lazyglobals.py +22 -9
- omlish/lang/params.py +17 -0
- omlish/lang/recursion.py +0 -1
- omlish/lang/sequences.py +124 -0
- omlish/lite/abstract.py +54 -24
- omlish/lite/asyncs.py +2 -2
- omlish/lite/attrops.py +2 -0
- omlish/lite/contextmanagers.py +4 -4
- omlish/lite/dataclasses.py +44 -0
- omlish/lite/maybes.py +8 -0
- omlish/lite/maysync.py +1 -5
- omlish/lite/pycharm.py +1 -1
- omlish/lite/typing.py +6 -0
- omlish/logs/all.py +1 -1
- omlish/logs/utils.py +1 -1
- omlish/manifests/loading.py +2 -1
- omlish/marshal/__init__.py +33 -13
- omlish/marshal/_dataclasses.py +2774 -0
- omlish/marshal/base/configs.py +12 -0
- omlish/marshal/base/contexts.py +36 -21
- omlish/marshal/base/funcs.py +8 -11
- omlish/marshal/base/options.py +8 -0
- omlish/marshal/base/registries.py +146 -44
- omlish/marshal/base/types.py +40 -16
- omlish/marshal/composite/iterables.py +33 -20
- omlish/marshal/composite/literals.py +20 -18
- omlish/marshal/composite/mappings.py +36 -23
- omlish/marshal/composite/maybes.py +29 -19
- omlish/marshal/composite/newtypes.py +16 -16
- omlish/marshal/composite/optionals.py +14 -14
- omlish/marshal/composite/special.py +15 -15
- omlish/marshal/composite/unions/__init__.py +0 -0
- omlish/marshal/composite/unions/literals.py +93 -0
- omlish/marshal/composite/unions/primitives.py +103 -0
- omlish/marshal/factories/invalidate.py +18 -68
- omlish/marshal/factories/method.py +26 -0
- omlish/marshal/factories/moduleimport/factories.py +22 -65
- omlish/marshal/factories/multi.py +13 -25
- omlish/marshal/factories/recursive.py +42 -56
- omlish/marshal/factories/typecache.py +29 -74
- omlish/marshal/factories/typemap.py +42 -43
- omlish/marshal/objects/dataclasses.py +129 -106
- omlish/marshal/objects/marshal.py +18 -14
- omlish/marshal/objects/namedtuples.py +48 -42
- omlish/marshal/objects/unmarshal.py +19 -15
- omlish/marshal/polymorphism/marshal.py +9 -11
- omlish/marshal/polymorphism/metadata.py +16 -5
- omlish/marshal/polymorphism/standard.py +13 -1
- omlish/marshal/polymorphism/unions.py +15 -105
- omlish/marshal/polymorphism/unmarshal.py +9 -10
- omlish/marshal/singular/enums.py +14 -18
- omlish/marshal/standard.py +10 -6
- omlish/marshal/trivial/any.py +1 -1
- omlish/marshal/trivial/forbidden.py +21 -26
- omlish/metadata.py +23 -1
- omlish/os/forkhooks.py +4 -0
- omlish/os/pidfiles/pinning.py +2 -2
- omlish/reflect/types.py +1 -0
- omlish/secrets/marshal.py +1 -1
- omlish/specs/jmespath/__init__.py +12 -3
- omlish/specs/jmespath/_dataclasses.py +2893 -0
- omlish/specs/jmespath/ast.py +1 -1
- omlish/specs/jsonrpc/__init__.py +13 -0
- omlish/specs/jsonrpc/_marshal.py +32 -23
- omlish/specs/jsonrpc/conns.py +10 -7
- omlish/specs/jsonschema/_marshal.py +1 -1
- omlish/specs/jsonschema/keywords/__init__.py +7 -0
- omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
- omlish/specs/openapi/_marshal.py +31 -22
- omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
- omlish/sql/queries/_marshal.py +2 -2
- omlish/sql/queries/rendering.py +1 -1
- omlish/sql/tabledefs/_marshal.py +1 -1
- omlish/subprocesses/base.py +4 -0
- omlish/subprocesses/editor.py +1 -1
- omlish/sync.py +155 -21
- omlish/term/alt.py +60 -0
- omlish/term/confirm.py +8 -8
- omlish/term/pager.py +235 -0
- omlish/term/terminfo.py +935 -0
- omlish/term/termstate.py +67 -0
- omlish/term/vt100/terminal.py +0 -3
- omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
- omlish/testing/pytest/plugins/skips.py +2 -1
- omlish/testing/unittest/main.py +3 -3
- omlish/text/docwrap/__init__.py +3 -0
- omlish/text/docwrap/__main__.py +11 -0
- omlish/text/docwrap/api.py +83 -0
- omlish/text/docwrap/cli.py +86 -0
- omlish/text/docwrap/groups.py +84 -0
- omlish/text/docwrap/lists.py +167 -0
- omlish/text/docwrap/parts.py +146 -0
- omlish/text/docwrap/reflowing.py +106 -0
- omlish/text/docwrap/rendering.py +151 -0
- omlish/text/docwrap/utils.py +11 -0
- omlish/text/docwrap/wrapping.py +59 -0
- omlish/text/filecache.py +2 -2
- omlish/text/lorem.py +6 -0
- omlish/text/parts.py +2 -2
- omlish/text/textwrap.py +51 -0
- omlish/typedvalues/marshal.py +85 -59
- omlish/typedvalues/values.py +2 -1
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/METADATA +29 -28
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/RECORD +222 -171
- omlish/dataclasses/impl/generation/mangling.py +0 -18
- omlish/funcs/match.py +0 -227
- omlish/marshal/factories/match.py +0 -34
- omlish/marshal/factories/simple.py +0 -28
- /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/top_level.txt +0 -0
omlish/http/clients/sync.py
CHANGED
|
@@ -5,11 +5,13 @@ import contextlib
|
|
|
5
5
|
import dataclasses as dc
|
|
6
6
|
import typing as ta
|
|
7
7
|
|
|
8
|
+
from ...io.readers import BufferedBytesReader
|
|
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
|
|
@@ -25,22 +27,21 @@ HttpClientT = ta.TypeVar('HttpClientT', bound='HttpClient')
|
|
|
25
27
|
@ta.final
|
|
26
28
|
@dc.dataclass(frozen=True) # kw_only=True
|
|
27
29
|
class StreamHttpResponse(BaseHttpResponse):
|
|
28
|
-
|
|
29
|
-
def read(self, /, n: int = -1) -> bytes: ...
|
|
30
|
+
_stream: ta.Optional[BufferedBytesReader] = None
|
|
30
31
|
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
raise TypeError
|
|
32
|
+
@property
|
|
33
|
+
def stream(self) -> 'BufferedBytesReader':
|
|
34
|
+
if (st := self._stream) is None:
|
|
35
|
+
raise TypeError('No data')
|
|
36
|
+
return st
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
@property
|
|
39
|
+
def has_data(self) -> bool:
|
|
40
|
+
return self._stream is not None
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
#
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
dataclass_maybe_post_init(super())
|
|
42
|
-
if isinstance(self.stream, StreamHttpResponse._NullStream):
|
|
43
|
-
raise TypeError(self.stream)
|
|
44
|
+
_closer: ta.Optional[ta.Callable[[], None]] = None
|
|
44
45
|
|
|
45
46
|
def __enter__(self: StreamHttpResponseT) -> StreamHttpResponseT:
|
|
46
47
|
return self
|
|
@@ -50,13 +51,13 @@ class StreamHttpResponse(BaseHttpResponse):
|
|
|
50
51
|
|
|
51
52
|
def close(self) -> None:
|
|
52
53
|
if (c := self._closer) is not None:
|
|
53
|
-
c()
|
|
54
|
+
c() # noqa
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
#
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
def
|
|
60
|
+
def close_http_client_response(resp: BaseHttpResponse) -> None:
|
|
60
61
|
if isinstance(resp, HttpResponse):
|
|
61
62
|
pass
|
|
62
63
|
|
|
@@ -68,7 +69,7 @@ def close_response(resp: BaseHttpResponse) -> None:
|
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
@contextlib.contextmanager
|
|
71
|
-
def
|
|
72
|
+
def closing_http_client_response(resp: BaseHttpResponseT) -> ta.Iterator[BaseHttpResponseT]:
|
|
72
73
|
if isinstance(resp, HttpResponse):
|
|
73
74
|
yield resp
|
|
74
75
|
return
|
|
@@ -81,15 +82,14 @@ def closing_response(resp: BaseHttpResponseT) -> ta.Iterator[BaseHttpResponseT]:
|
|
|
81
82
|
raise TypeError(resp)
|
|
82
83
|
|
|
83
84
|
|
|
84
|
-
def
|
|
85
|
+
def read_http_client_response(resp: BaseHttpResponse) -> HttpResponse:
|
|
85
86
|
if isinstance(resp, HttpResponse):
|
|
86
87
|
return resp
|
|
87
88
|
|
|
88
89
|
elif isinstance(resp, StreamHttpResponse):
|
|
89
|
-
data = resp.stream.read()
|
|
90
90
|
return HttpResponse(**{
|
|
91
|
-
**{k: v for k, v in dataclass_shallow_asdict(resp).items() if k not in ('
|
|
92
|
-
'data':
|
|
91
|
+
**{k: v for k, v in dataclass_shallow_asdict(resp).items() if k not in ('_stream', '_closer')},
|
|
92
|
+
**({'data': resp.stream.readall()} if resp.has_data else {}),
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
else:
|
|
@@ -99,7 +99,7 @@ def read_response(resp: BaseHttpResponse) -> HttpResponse:
|
|
|
99
99
|
##
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
class HttpClient(Abstract):
|
|
102
|
+
class HttpClient(BaseHttpClient, Abstract):
|
|
103
103
|
def __enter__(self: HttpClientT) -> HttpClientT:
|
|
104
104
|
return self
|
|
105
105
|
|
|
@@ -110,21 +110,27 @@ class HttpClient(Abstract):
|
|
|
110
110
|
self,
|
|
111
111
|
req: HttpRequest,
|
|
112
112
|
*,
|
|
113
|
+
context: ta.Optional[HttpClientContext] = None,
|
|
113
114
|
check: bool = False,
|
|
114
115
|
) -> HttpResponse:
|
|
115
|
-
with
|
|
116
|
+
with closing_http_client_response(self.stream_request(
|
|
116
117
|
req,
|
|
118
|
+
context=context,
|
|
117
119
|
check=check,
|
|
118
120
|
)) as resp:
|
|
119
|
-
return
|
|
121
|
+
return read_http_client_response(resp)
|
|
120
122
|
|
|
121
123
|
def stream_request(
|
|
122
124
|
self,
|
|
123
125
|
req: HttpRequest,
|
|
124
126
|
*,
|
|
127
|
+
context: ta.Optional[HttpClientContext] = None,
|
|
125
128
|
check: bool = False,
|
|
126
129
|
) -> StreamHttpResponse:
|
|
127
|
-
|
|
130
|
+
if context is None:
|
|
131
|
+
context = HttpClientContext()
|
|
132
|
+
|
|
133
|
+
resp = self._stream_request(context, req)
|
|
128
134
|
|
|
129
135
|
try:
|
|
130
136
|
if check and not resp.is_success:
|
|
@@ -132,14 +138,14 @@ class HttpClient(Abstract):
|
|
|
132
138
|
cause = resp.underlying
|
|
133
139
|
else:
|
|
134
140
|
cause = None
|
|
135
|
-
raise HttpStatusError(
|
|
141
|
+
raise HttpStatusError(read_http_client_response(resp)) from cause # noqa
|
|
136
142
|
|
|
137
143
|
except Exception:
|
|
138
|
-
|
|
144
|
+
close_http_client_response(resp)
|
|
139
145
|
raise
|
|
140
146
|
|
|
141
147
|
return resp
|
|
142
148
|
|
|
143
149
|
@abc.abstractmethod
|
|
144
|
-
def _stream_request(self, req: HttpRequest) -> StreamHttpResponse:
|
|
150
|
+
def _stream_request(self, ctx: HttpClientContext, req: HttpRequest) -> StreamHttpResponse:
|
|
145
151
|
raise NotImplementedError
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# ruff: noqa: UP043 UP045
|
|
2
|
+
# @omlish-lite
|
|
3
|
+
import dataclasses as dc
|
|
4
|
+
|
|
5
|
+
from .asyncs import AsyncHttpClient
|
|
6
|
+
from .asyncs import AsyncStreamHttpResponse
|
|
7
|
+
from .base import HttpClientContext
|
|
8
|
+
from .base import HttpRequest
|
|
9
|
+
from .sync import HttpClient
|
|
10
|
+
from .sync import StreamHttpResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SyncAsyncHttpClient(AsyncHttpClient):
|
|
17
|
+
def __init__(self, client: HttpClient) -> None:
|
|
18
|
+
super().__init__()
|
|
19
|
+
|
|
20
|
+
self._client = client
|
|
21
|
+
|
|
22
|
+
@dc.dataclass(frozen=True)
|
|
23
|
+
class _StreamAdapter:
|
|
24
|
+
ul: StreamHttpResponse
|
|
25
|
+
|
|
26
|
+
async def read1(self, n: int = -1, /) -> bytes:
|
|
27
|
+
return self.ul.stream.read1(n)
|
|
28
|
+
|
|
29
|
+
async def read(self, n: int = -1, /) -> bytes:
|
|
30
|
+
return self.ul.stream.read(n)
|
|
31
|
+
|
|
32
|
+
async def readall(self) -> bytes:
|
|
33
|
+
return self.ul.stream.readall()
|
|
34
|
+
|
|
35
|
+
async def close(self) -> None:
|
|
36
|
+
self.ul.close()
|
|
37
|
+
|
|
38
|
+
async def _stream_request(self, ctx: HttpClientContext, req: HttpRequest) -> AsyncStreamHttpResponse:
|
|
39
|
+
resp = self._client.stream_request(req, context=ctx)
|
|
40
|
+
return AsyncStreamHttpResponse(
|
|
41
|
+
status=resp.status,
|
|
42
|
+
headers=resp.headers,
|
|
43
|
+
request=req,
|
|
44
|
+
underlying=resp,
|
|
45
|
+
**(dict( # type: ignore
|
|
46
|
+
_stream=(adapter := self._StreamAdapter(resp)),
|
|
47
|
+
_closer=adapter.close,
|
|
48
|
+
) if resp.has_data else {}),
|
|
49
|
+
)
|
omlish/http/clients/urllib.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
# ruff: noqa: UP043 UP045
|
|
2
|
+
# @omlish-lite
|
|
1
3
|
import http.client
|
|
2
4
|
import typing as ta
|
|
3
5
|
import urllib.error
|
|
4
6
|
import urllib.request
|
|
5
7
|
|
|
8
|
+
from ...io.buffers import ReadableListBuffer
|
|
6
9
|
from ..headers import HttpHeaders
|
|
7
10
|
from .base import DEFAULT_ENCODING
|
|
11
|
+
from .base import HttpClientContext
|
|
8
12
|
from .base import HttpClientError
|
|
9
13
|
from .base import HttpRequest
|
|
10
14
|
from .sync import HttpClient
|
|
@@ -39,7 +43,7 @@ class UrllibHttpClient(HttpClient):
|
|
|
39
43
|
data=d,
|
|
40
44
|
)
|
|
41
45
|
|
|
42
|
-
def _stream_request(self, req: HttpRequest) -> StreamHttpResponse:
|
|
46
|
+
def _stream_request(self, ctx: HttpClientContext, req: HttpRequest) -> StreamHttpResponse:
|
|
43
47
|
try:
|
|
44
48
|
resp = urllib.request.urlopen( # noqa
|
|
45
49
|
self._build_request(req),
|
|
@@ -53,7 +57,6 @@ class UrllibHttpClient(HttpClient):
|
|
|
53
57
|
headers=HttpHeaders(e.headers.items()),
|
|
54
58
|
request=req,
|
|
55
59
|
underlying=e,
|
|
56
|
-
stream=e, # noqa
|
|
57
60
|
_closer=e.close,
|
|
58
61
|
)
|
|
59
62
|
|
|
@@ -70,7 +73,7 @@ class UrllibHttpClient(HttpClient):
|
|
|
70
73
|
headers=HttpHeaders(resp.headers.items()),
|
|
71
74
|
request=req,
|
|
72
75
|
underlying=resp,
|
|
73
|
-
|
|
76
|
+
_stream=ReadableListBuffer().new_buffered_reader(resp),
|
|
74
77
|
_closer=resp.close,
|
|
75
78
|
)
|
|
76
79
|
|
|
@@ -116,10 +116,10 @@ class CoroHttpClientConnection:
|
|
|
116
116
|
_http_version = 11
|
|
117
117
|
_http_version_str = 'HTTP/1.1'
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
HTTP_PORT: ta.ClassVar[int] = 80
|
|
120
|
+
HTTPS_PORT: ta.ClassVar[int] = 443
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
DEFAULT_PORT: ta.ClassVar[int] = HTTP_PORT
|
|
123
123
|
|
|
124
124
|
class _NOT_SET: # noqa
|
|
125
125
|
def __new__(cls, *args, **kwargs): # noqa
|
|
@@ -139,6 +139,7 @@ class CoroHttpClientConnection:
|
|
|
139
139
|
source_address: ta.Optional[str] = None,
|
|
140
140
|
block_size: int = 8192,
|
|
141
141
|
auto_open: bool = True,
|
|
142
|
+
default_port: ta.Optional[int] = None,
|
|
142
143
|
) -> None:
|
|
143
144
|
super().__init__()
|
|
144
145
|
|
|
@@ -146,6 +147,9 @@ class CoroHttpClientConnection:
|
|
|
146
147
|
self._source_address = source_address
|
|
147
148
|
self._block_size = block_size
|
|
148
149
|
self._auto_open = auto_open
|
|
150
|
+
if default_port is None:
|
|
151
|
+
default_port = self.DEFAULT_PORT
|
|
152
|
+
self._default_port = default_port
|
|
149
153
|
|
|
150
154
|
self._connected = False
|
|
151
155
|
self._buffer: ta.List[bytes] = []
|
|
@@ -162,6 +166,10 @@ class CoroHttpClientConnection:
|
|
|
162
166
|
|
|
163
167
|
CoroHttpClientValidation.validate_host(self._host)
|
|
164
168
|
|
|
169
|
+
@property
|
|
170
|
+
def http_version(self) -> int:
|
|
171
|
+
return self._http_version
|
|
172
|
+
|
|
165
173
|
#
|
|
166
174
|
|
|
167
175
|
def _get_hostport(self, host: str, port: ta.Optional[int]) -> ta.Tuple[str, int]:
|
|
@@ -173,12 +181,12 @@ class CoroHttpClientConnection:
|
|
|
173
181
|
port = int(host[i + 1:])
|
|
174
182
|
except ValueError:
|
|
175
183
|
if host[i + 1:] == '': # http://foo.com:/ == http://foo.com/
|
|
176
|
-
port = self.
|
|
184
|
+
port = self._default_port
|
|
177
185
|
else:
|
|
178
186
|
raise CoroHttpClientErrors.InvalidUrlError(f"non-numeric port: '{host[i + 1:]}'") from None
|
|
179
187
|
host = host[:i]
|
|
180
188
|
else:
|
|
181
|
-
port = self.
|
|
189
|
+
port = self._default_port
|
|
182
190
|
|
|
183
191
|
if host and host[0] == '[' and host[-1] == ']':
|
|
184
192
|
host = host[1:-1]
|
|
@@ -286,6 +294,7 @@ class CoroHttpClientConnection:
|
|
|
286
294
|
source_address=self._source_address,
|
|
287
295
|
**(dict(timeout=self._timeout) if self._timeout is not self._NOT_SET else {}),
|
|
288
296
|
),
|
|
297
|
+
server_hostname=self._tunnel_host if self._tunnel_host else self._host,
|
|
289
298
|
)))
|
|
290
299
|
|
|
291
300
|
self._connected = True
|
|
@@ -526,7 +535,7 @@ class CoroHttpClientConnection:
|
|
|
526
535
|
if ':' in host:
|
|
527
536
|
host_enc = self._strip_ipv6_iface(host_enc)
|
|
528
537
|
|
|
529
|
-
if port == self.
|
|
538
|
+
if port == self._default_port:
|
|
530
539
|
self.put_header('Host', host_enc)
|
|
531
540
|
else:
|
|
532
541
|
self.put_header('Host', f"{host_enc.decode('ascii')}:{port}")
|
omlish/http/coro/io.py
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .api import ( # noqa
|
|
2
|
+
Api,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
from .app import ( # noqa
|
|
6
|
+
AppRunParams,
|
|
7
|
+
AppRunner,
|
|
8
|
+
|
|
9
|
+
ViewFunc,
|
|
10
|
+
BeforeRequestFunc,
|
|
11
|
+
AfterRequestFunc,
|
|
12
|
+
App,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .cvs import ( # noqa
|
|
16
|
+
CvLookupError,
|
|
17
|
+
|
|
18
|
+
Cvs,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .requests import ( # noqa
|
|
22
|
+
Request,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .responses import ( # noqa
|
|
26
|
+
ResponseData,
|
|
27
|
+
ResponseStatus,
|
|
28
|
+
ResponseHeaders,
|
|
29
|
+
|
|
30
|
+
Response,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from .routes import ( # noqa
|
|
34
|
+
RouteKey,
|
|
35
|
+
Route,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from .types import ( # noqa
|
|
39
|
+
ImmutableMultiDict,
|
|
40
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from ... import lang
|
|
5
|
+
from ._compat import compat
|
|
6
|
+
from .app import App
|
|
7
|
+
from .cvs import Cvs
|
|
8
|
+
from .requests import Request
|
|
9
|
+
from .responses import Response
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Api:
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
*,
|
|
19
|
+
base_app_cls: type[App] = App,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__()
|
|
22
|
+
|
|
23
|
+
self._base_app_cls = base_app_cls
|
|
24
|
+
|
|
25
|
+
self._lock = threading.RLock()
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def _cls_name_repr(self) -> str:
|
|
29
|
+
return f'{self.__class__.__name__}@{id(self):x}'
|
|
30
|
+
|
|
31
|
+
def __repr__(self) -> str:
|
|
32
|
+
return f'{self._cls_name_repr}()'
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# app cls
|
|
36
|
+
|
|
37
|
+
_app_cls: type[App]
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def app_cls(self) -> type[App]:
|
|
41
|
+
try:
|
|
42
|
+
return self._app_cls
|
|
43
|
+
except AttributeError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
with self._lock:
|
|
47
|
+
try:
|
|
48
|
+
return self._app_cls
|
|
49
|
+
except AttributeError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
self._app_cls = lang.new_type( # noqa
|
|
53
|
+
f'{self._base_app_cls.__name__}<{self._cls_name_repr}>',
|
|
54
|
+
(self._base_app_cls,),
|
|
55
|
+
{'_api': self},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return self._app_cls
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
@compat
|
|
62
|
+
def Flask(self) -> type[App]: # noqa
|
|
63
|
+
return self.app_cls
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# helpers
|
|
67
|
+
|
|
68
|
+
def abort(self, code: int | Response, *args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
|
|
69
|
+
raise NotImplementedError
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# cv's
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
@compat
|
|
76
|
+
def request(self) -> Request:
|
|
77
|
+
return Cvs.REQUEST.get()
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# type aliases - must be last
|
|
81
|
+
|
|
82
|
+
Response = Response
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from ... import check
|
|
4
|
+
from ... import dataclasses as dc
|
|
5
|
+
from ._compat import compat
|
|
6
|
+
from .cvs import Cvs
|
|
7
|
+
from .responses import Response
|
|
8
|
+
from .routes import Route
|
|
9
|
+
from .routes import RouteKey
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if ta.TYPE_CHECKING:
|
|
13
|
+
from .api import Api
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
T = ta.TypeVar('T')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
23
|
+
class AppRunParams:
|
|
24
|
+
app: 'App'
|
|
25
|
+
|
|
26
|
+
port: int | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
AppRunner: ta.TypeAlias = ta.Callable[[AppRunParams], None]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
ViewFunc: ta.TypeAlias = ta.Callable[[], str]
|
|
36
|
+
|
|
37
|
+
BeforeRequestFunc: ta.TypeAlias = ta.Callable[[], Response | None]
|
|
38
|
+
AfterRequestFunc: ta.TypeAlias = ta.Callable[[ta.Any], ta.Any]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class App:
|
|
45
|
+
_api: ta.ClassVar['Api']
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
import_name: str,
|
|
50
|
+
) -> None:
|
|
51
|
+
check.not_none(self._api)
|
|
52
|
+
|
|
53
|
+
super().__init__()
|
|
54
|
+
|
|
55
|
+
self._import_name = check.non_empty_str(import_name)
|
|
56
|
+
|
|
57
|
+
self._routes: set[Route] = set()
|
|
58
|
+
self._routes_by_key: dict[RouteKey, Route] = {}
|
|
59
|
+
|
|
60
|
+
self._view_funcs_by_endpoint: dict[str, ViewFunc] = {}
|
|
61
|
+
|
|
62
|
+
self._before_request_funcs: list[BeforeRequestFunc] = []
|
|
63
|
+
self._after_request_funcs: list[AfterRequestFunc] = []
|
|
64
|
+
|
|
65
|
+
def __repr__(self) -> str:
|
|
66
|
+
return f'{self.__class__.__name__}@{id(self):x}({self._import_name!r})'
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def import_name(self) -> str:
|
|
70
|
+
return self._import_name
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# routing
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def routes(self) -> ta.AbstractSet[Route]:
|
|
77
|
+
return self._routes
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def routes_by_key(self) -> ta.Mapping[RouteKey, Route]:
|
|
81
|
+
return self._routes_by_key
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def view_funcs_by_endpoint(self) -> ta.Mapping[str, ViewFunc]:
|
|
85
|
+
return self._view_funcs_by_endpoint
|
|
86
|
+
|
|
87
|
+
@compat
|
|
88
|
+
def get(self, rule: str, **options: ta.Any) -> ta.Callable[[T], T]:
|
|
89
|
+
return self._method_route('GET', rule, **options)
|
|
90
|
+
|
|
91
|
+
@compat
|
|
92
|
+
def post(self, rule: str, **options: ta.Any) -> ta.Callable[[T], T]:
|
|
93
|
+
return self._method_route('POST', rule, **options)
|
|
94
|
+
|
|
95
|
+
@compat
|
|
96
|
+
def put(self, rule: str, **options: ta.Any) -> ta.Callable[[T], T]:
|
|
97
|
+
return self._method_route('PUT', rule, **options)
|
|
98
|
+
|
|
99
|
+
@compat
|
|
100
|
+
def delete(self, rule: str, **options: ta.Any) -> ta.Callable[[T], T]:
|
|
101
|
+
return self._method_route('DELETE', rule, **options)
|
|
102
|
+
|
|
103
|
+
@compat
|
|
104
|
+
def patch(self, rule: str, **options: ta.Any) -> ta.Callable[[T], T]:
|
|
105
|
+
return self._method_route('PATCH', rule, **options)
|
|
106
|
+
|
|
107
|
+
def _method_route(
|
|
108
|
+
self,
|
|
109
|
+
method: str,
|
|
110
|
+
rule: str,
|
|
111
|
+
**options: ta.Any,
|
|
112
|
+
) -> ta.Callable[[T], T]:
|
|
113
|
+
return self.route(
|
|
114
|
+
rule,
|
|
115
|
+
methods=[method],
|
|
116
|
+
**options,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@compat
|
|
120
|
+
def route(
|
|
121
|
+
self,
|
|
122
|
+
rule: str,
|
|
123
|
+
**options: ta.Any,
|
|
124
|
+
) -> ta.Callable[[T], T]:
|
|
125
|
+
def inner(fn):
|
|
126
|
+
endpoint = options.pop('endpoint', None)
|
|
127
|
+
self.add_url_rule(rule, endpoint, fn, **options)
|
|
128
|
+
return fn
|
|
129
|
+
|
|
130
|
+
return inner
|
|
131
|
+
|
|
132
|
+
@compat
|
|
133
|
+
def add_url_rule(
|
|
134
|
+
self,
|
|
135
|
+
rule: str,
|
|
136
|
+
endpoint: str | None = None,
|
|
137
|
+
view_func: ViewFunc | None = None,
|
|
138
|
+
*,
|
|
139
|
+
methods: ta.Iterable[str] | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
check.arg(check.non_empty_str(rule).startswith('/'))
|
|
142
|
+
|
|
143
|
+
if endpoint is None:
|
|
144
|
+
endpoint = check.not_none(view_func).__name__
|
|
145
|
+
|
|
146
|
+
if methods is None:
|
|
147
|
+
methods = ['GET']
|
|
148
|
+
else:
|
|
149
|
+
methods = [check.non_empty_str(m) for m in check.not_isinstance(methods, str)]
|
|
150
|
+
methods = check.not_empty([s.upper() for s in methods])
|
|
151
|
+
|
|
152
|
+
#
|
|
153
|
+
|
|
154
|
+
route = Route(
|
|
155
|
+
rule=rule,
|
|
156
|
+
endpoint=endpoint,
|
|
157
|
+
methods=frozenset(methods),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
#
|
|
161
|
+
|
|
162
|
+
for rk in route.keys:
|
|
163
|
+
check.not_in(rk, self._routes_by_key)
|
|
164
|
+
|
|
165
|
+
if view_func is not None:
|
|
166
|
+
check.not_in(endpoint, self._view_funcs_by_endpoint)
|
|
167
|
+
|
|
168
|
+
#
|
|
169
|
+
|
|
170
|
+
self._routes.add(route)
|
|
171
|
+
for rk in route.keys:
|
|
172
|
+
self._routes_by_key[rk] = route
|
|
173
|
+
|
|
174
|
+
if view_func is not None:
|
|
175
|
+
self._view_funcs_by_endpoint[endpoint] = view_func
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# hooks
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def before_request_funcs(self) -> ta.Sequence[BeforeRequestFunc]:
|
|
182
|
+
return self._before_request_funcs
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def after_request_funcs(self) -> ta.Sequence[AfterRequestFunc]:
|
|
186
|
+
return self._after_request_funcs
|
|
187
|
+
|
|
188
|
+
def before_request(self, func: T) -> T: # BeforeRequestFunc
|
|
189
|
+
self._before_request_funcs.append(ta.cast(BeforeRequestFunc, check.callable(func)))
|
|
190
|
+
return func
|
|
191
|
+
|
|
192
|
+
def after_request(self, func: T) -> T: # AfterRequestFunc
|
|
193
|
+
self._after_request_funcs.append(ta.cast(AfterRequestFunc, check.callable(func)))
|
|
194
|
+
return func
|
|
195
|
+
|
|
196
|
+
##
|
|
197
|
+
# running
|
|
198
|
+
|
|
199
|
+
@compat
|
|
200
|
+
def run(self, **kwargs: ta.Any) -> None:
|
|
201
|
+
params = AppRunParams(app=self, **kwargs)
|
|
202
|
+
runner = Cvs.APP_RUNNER.get()
|
|
203
|
+
check.not_none(runner)(params)
|