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.

Files changed (226) hide show
  1. omlish/.omlish-manifests.json +12 -0
  2. omlish/__about__.py +15 -15
  3. omlish/argparse/all.py +17 -9
  4. omlish/argparse/cli.py +16 -3
  5. omlish/argparse/utils.py +21 -0
  6. omlish/asyncs/asyncio/rlock.py +110 -0
  7. omlish/asyncs/asyncio/sync.py +43 -0
  8. omlish/asyncs/asyncio/utils.py +2 -0
  9. omlish/asyncs/sync.py +25 -0
  10. omlish/bootstrap/_marshal.py +1 -1
  11. omlish/bootstrap/diag.py +12 -21
  12. omlish/bootstrap/main.py +2 -5
  13. omlish/bootstrap/sys.py +27 -28
  14. omlish/cexts/__init__.py +0 -0
  15. omlish/cexts/include/omlish/omlish.hh +1 -0
  16. omlish/collections/__init__.py +13 -1
  17. omlish/collections/attrregistry.py +210 -0
  18. omlish/collections/cache/impl.py +1 -0
  19. omlish/collections/identity.py +1 -0
  20. omlish/collections/mappings.py +28 -0
  21. omlish/collections/trie.py +5 -1
  22. omlish/collections/utils.py +77 -0
  23. omlish/concurrent/all.py +2 -1
  24. omlish/concurrent/futures.py +25 -0
  25. omlish/concurrent/threadlets.py +1 -1
  26. omlish/daemons/reparent.py +2 -3
  27. omlish/daemons/spawning.py +2 -3
  28. omlish/dataclasses/__init__.py +2 -0
  29. omlish/dataclasses/impl/api/classes/decorator.py +3 -0
  30. omlish/dataclasses/impl/api/classes/make.py +3 -0
  31. omlish/dataclasses/impl/concerns/repr.py +15 -2
  32. omlish/dataclasses/impl/configs.py +97 -37
  33. omlish/dataclasses/impl/generation/compilation.py +21 -19
  34. omlish/dataclasses/impl/generation/globals.py +1 -0
  35. omlish/dataclasses/impl/generation/ops.py +1 -0
  36. omlish/dataclasses/impl/generation/processor.py +105 -24
  37. omlish/dataclasses/impl/processing/base.py +8 -0
  38. omlish/dataclasses/impl/processing/driving.py +8 -8
  39. omlish/dataclasses/specs.py +1 -0
  40. omlish/dataclasses/tools/modifiers.py +5 -0
  41. omlish/diag/cmds/__init__.py +0 -0
  42. omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
  43. omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
  44. omlish/diag/{ps.py → cmds/ps.py} +6 -6
  45. omlish/diag/pycharm.py +16 -2
  46. omlish/diag/pydevd.py +58 -40
  47. omlish/diag/replserver/console.py +1 -1
  48. omlish/dispatch/__init__.py +18 -12
  49. omlish/dispatch/methods.py +50 -140
  50. omlish/dom/rendering.py +1 -1
  51. omlish/formats/dotenv.py +1 -1
  52. omlish/formats/json/stream/__init__.py +13 -0
  53. omlish/funcs/guard.py +225 -0
  54. omlish/graphs/dot/rendering.py +1 -1
  55. omlish/http/all.py +44 -4
  56. omlish/http/clients/asyncs.py +33 -27
  57. omlish/http/clients/base.py +17 -1
  58. omlish/http/clients/coro/__init__.py +0 -0
  59. omlish/http/clients/coro/sync.py +171 -0
  60. omlish/http/clients/default.py +208 -29
  61. omlish/http/clients/executor.py +56 -0
  62. omlish/http/clients/httpx.py +72 -11
  63. omlish/http/clients/middleware.py +181 -0
  64. omlish/http/clients/sync.py +33 -27
  65. omlish/http/clients/syncasync.py +49 -0
  66. omlish/http/clients/urllib.py +6 -3
  67. omlish/http/coro/client/connection.py +15 -6
  68. omlish/http/coro/io.py +2 -0
  69. omlish/http/flasky/__init__.py +40 -0
  70. omlish/http/flasky/_compat.py +2 -0
  71. omlish/http/flasky/api.py +82 -0
  72. omlish/http/flasky/app.py +203 -0
  73. omlish/http/flasky/cvs.py +59 -0
  74. omlish/http/flasky/requests.py +20 -0
  75. omlish/http/flasky/responses.py +23 -0
  76. omlish/http/flasky/routes.py +23 -0
  77. omlish/http/flasky/types.py +15 -0
  78. omlish/http/urls.py +67 -0
  79. omlish/inject/__init__.py +38 -18
  80. omlish/inject/_dataclasses.py +4986 -0
  81. omlish/inject/binder.py +4 -48
  82. omlish/inject/elements.py +27 -0
  83. omlish/inject/helpers/__init__.py +0 -0
  84. omlish/inject/{utils.py → helpers/constfn.py} +3 -3
  85. omlish/inject/{tags.py → helpers/id.py} +2 -2
  86. omlish/inject/helpers/multis.py +143 -0
  87. omlish/inject/helpers/wrappers.py +54 -0
  88. omlish/inject/impl/elements.py +47 -17
  89. omlish/inject/impl/injector.py +20 -19
  90. omlish/inject/impl/inspect.py +10 -1
  91. omlish/inject/impl/maysync.py +3 -4
  92. omlish/inject/impl/multis.py +3 -0
  93. omlish/inject/impl/sync.py +3 -4
  94. omlish/inject/injector.py +31 -2
  95. omlish/inject/inspect.py +35 -0
  96. omlish/inject/maysync.py +2 -4
  97. omlish/inject/multis.py +8 -0
  98. omlish/inject/overrides.py +3 -3
  99. omlish/inject/privates.py +6 -0
  100. omlish/inject/providers.py +3 -2
  101. omlish/inject/sync.py +5 -4
  102. omlish/io/buffers.py +118 -0
  103. omlish/io/readers.py +29 -0
  104. omlish/iterators/transforms.py +2 -2
  105. omlish/lang/__init__.py +178 -97
  106. omlish/lang/_asyncs.cc +186 -0
  107. omlish/lang/asyncs.py +17 -0
  108. omlish/lang/casing.py +11 -0
  109. omlish/lang/contextmanagers.py +28 -4
  110. omlish/lang/functions.py +31 -22
  111. omlish/lang/imports/_capture.cc +11 -9
  112. omlish/lang/imports/capture.py +363 -170
  113. omlish/lang/imports/proxy.py +455 -152
  114. omlish/lang/lazyglobals.py +22 -9
  115. omlish/lang/params.py +17 -0
  116. omlish/lang/recursion.py +0 -1
  117. omlish/lang/sequences.py +124 -0
  118. omlish/lite/abstract.py +54 -24
  119. omlish/lite/asyncs.py +2 -2
  120. omlish/lite/attrops.py +2 -0
  121. omlish/lite/contextmanagers.py +4 -4
  122. omlish/lite/dataclasses.py +44 -0
  123. omlish/lite/maybes.py +8 -0
  124. omlish/lite/maysync.py +1 -5
  125. omlish/lite/pycharm.py +1 -1
  126. omlish/lite/typing.py +6 -0
  127. omlish/logs/all.py +1 -1
  128. omlish/logs/utils.py +1 -1
  129. omlish/manifests/loading.py +2 -1
  130. omlish/marshal/__init__.py +33 -13
  131. omlish/marshal/_dataclasses.py +2774 -0
  132. omlish/marshal/base/configs.py +12 -0
  133. omlish/marshal/base/contexts.py +36 -21
  134. omlish/marshal/base/funcs.py +8 -11
  135. omlish/marshal/base/options.py +8 -0
  136. omlish/marshal/base/registries.py +146 -44
  137. omlish/marshal/base/types.py +40 -16
  138. omlish/marshal/composite/iterables.py +33 -20
  139. omlish/marshal/composite/literals.py +20 -18
  140. omlish/marshal/composite/mappings.py +36 -23
  141. omlish/marshal/composite/maybes.py +29 -19
  142. omlish/marshal/composite/newtypes.py +16 -16
  143. omlish/marshal/composite/optionals.py +14 -14
  144. omlish/marshal/composite/special.py +15 -15
  145. omlish/marshal/composite/unions/__init__.py +0 -0
  146. omlish/marshal/composite/unions/literals.py +93 -0
  147. omlish/marshal/composite/unions/primitives.py +103 -0
  148. omlish/marshal/factories/invalidate.py +18 -68
  149. omlish/marshal/factories/method.py +26 -0
  150. omlish/marshal/factories/moduleimport/factories.py +22 -65
  151. omlish/marshal/factories/multi.py +13 -25
  152. omlish/marshal/factories/recursive.py +42 -56
  153. omlish/marshal/factories/typecache.py +29 -74
  154. omlish/marshal/factories/typemap.py +42 -43
  155. omlish/marshal/objects/dataclasses.py +129 -106
  156. omlish/marshal/objects/marshal.py +18 -14
  157. omlish/marshal/objects/namedtuples.py +48 -42
  158. omlish/marshal/objects/unmarshal.py +19 -15
  159. omlish/marshal/polymorphism/marshal.py +9 -11
  160. omlish/marshal/polymorphism/metadata.py +16 -5
  161. omlish/marshal/polymorphism/standard.py +13 -1
  162. omlish/marshal/polymorphism/unions.py +15 -105
  163. omlish/marshal/polymorphism/unmarshal.py +9 -10
  164. omlish/marshal/singular/enums.py +14 -18
  165. omlish/marshal/standard.py +10 -6
  166. omlish/marshal/trivial/any.py +1 -1
  167. omlish/marshal/trivial/forbidden.py +21 -26
  168. omlish/metadata.py +23 -1
  169. omlish/os/forkhooks.py +4 -0
  170. omlish/os/pidfiles/pinning.py +2 -2
  171. omlish/reflect/types.py +1 -0
  172. omlish/secrets/marshal.py +1 -1
  173. omlish/specs/jmespath/__init__.py +12 -3
  174. omlish/specs/jmespath/_dataclasses.py +2893 -0
  175. omlish/specs/jmespath/ast.py +1 -1
  176. omlish/specs/jsonrpc/__init__.py +13 -0
  177. omlish/specs/jsonrpc/_marshal.py +32 -23
  178. omlish/specs/jsonrpc/conns.py +10 -7
  179. omlish/specs/jsonschema/_marshal.py +1 -1
  180. omlish/specs/jsonschema/keywords/__init__.py +7 -0
  181. omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
  182. omlish/specs/openapi/_marshal.py +31 -22
  183. omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
  184. omlish/sql/queries/_marshal.py +2 -2
  185. omlish/sql/queries/rendering.py +1 -1
  186. omlish/sql/tabledefs/_marshal.py +1 -1
  187. omlish/subprocesses/base.py +4 -0
  188. omlish/subprocesses/editor.py +1 -1
  189. omlish/sync.py +155 -21
  190. omlish/term/alt.py +60 -0
  191. omlish/term/confirm.py +8 -8
  192. omlish/term/pager.py +235 -0
  193. omlish/term/terminfo.py +935 -0
  194. omlish/term/termstate.py +67 -0
  195. omlish/term/vt100/terminal.py +0 -3
  196. omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
  197. omlish/testing/pytest/plugins/skips.py +2 -1
  198. omlish/testing/unittest/main.py +3 -3
  199. omlish/text/docwrap/__init__.py +3 -0
  200. omlish/text/docwrap/__main__.py +11 -0
  201. omlish/text/docwrap/api.py +83 -0
  202. omlish/text/docwrap/cli.py +86 -0
  203. omlish/text/docwrap/groups.py +84 -0
  204. omlish/text/docwrap/lists.py +167 -0
  205. omlish/text/docwrap/parts.py +146 -0
  206. omlish/text/docwrap/reflowing.py +106 -0
  207. omlish/text/docwrap/rendering.py +151 -0
  208. omlish/text/docwrap/utils.py +11 -0
  209. omlish/text/docwrap/wrapping.py +59 -0
  210. omlish/text/filecache.py +2 -2
  211. omlish/text/lorem.py +6 -0
  212. omlish/text/parts.py +2 -2
  213. omlish/text/textwrap.py +51 -0
  214. omlish/typedvalues/marshal.py +85 -59
  215. omlish/typedvalues/values.py +2 -1
  216. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/METADATA +29 -28
  217. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/RECORD +222 -171
  218. omlish/dataclasses/impl/generation/mangling.py +0 -18
  219. omlish/funcs/match.py +0 -227
  220. omlish/marshal/factories/match.py +0 -34
  221. omlish/marshal/factories/simple.py +0 -28
  222. /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
  223. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/WHEEL +0 -0
  224. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/entry_points.txt +0 -0
  225. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/licenses/LICENSE +0 -0
  226. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/top_level.txt +0 -0
@@ -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
- class Stream(ta.Protocol):
29
- def read(self, /, n: int = -1) -> bytes: ...
30
+ _stream: ta.Optional[BufferedBytesReader] = None
30
31
 
31
- @ta.final
32
- class _NullStream:
33
- def read(self, /, n: int = -1) -> bytes:
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
- stream: Stream = _NullStream()
38
+ @property
39
+ def has_data(self) -> bool:
40
+ return self._stream is not None
37
41
 
38
- _closer: ta.Optional[ta.Callable[[], None]] = None
42
+ #
39
43
 
40
- def __post_init__(self) -> None:
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 close_response(resp: BaseHttpResponse) -> None:
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 closing_response(resp: BaseHttpResponseT) -> ta.Iterator[BaseHttpResponseT]:
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 read_response(resp: BaseHttpResponse) -> HttpResponse:
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 ('stream', '_closer')},
92
- 'data': 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 closing_response(self.stream_request(
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 read_response(resp)
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
- resp = self._stream_request(req)
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(read_response(resp)) from cause # noqa
141
+ raise HttpStatusError(read_http_client_response(resp)) from cause # noqa
136
142
 
137
143
  except Exception:
138
- close_response(resp)
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
+ )
@@ -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
- stream=resp,
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
- http_port: ta.ClassVar[int] = 80
120
- https_port: ta.ClassVar[int] = 443
119
+ HTTP_PORT: ta.ClassVar[int] = 80
120
+ HTTPS_PORT: ta.ClassVar[int] = 443
121
121
 
122
- default_port = http_port
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.default_port
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.default_port
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.default_port:
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
@@ -37,6 +37,8 @@ class CoroHttpIo:
37
37
  args: ta.Tuple[ta.Any, ...]
38
38
  kwargs: ta.Optional[ta.Dict[str, ta.Any]] = None
39
39
 
40
+ server_hostname: ta.Optional[str] = None
41
+
40
42
  #
41
43
 
42
44
  class CloseIo(Io):
@@ -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,2 @@
1
+ def compat(obj):
2
+ return obj
@@ -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)