tigrcorn-runtime 0.3.16.dev5__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.
- tigrcorn_runtime/__init__.py +20 -0
- tigrcorn_runtime/api.py +351 -0
- tigrcorn_runtime/app_interfaces.py +105 -0
- tigrcorn_runtime/cli.py +191 -0
- tigrcorn_runtime/embedded.py +77 -0
- tigrcorn_runtime/py.typed +1 -0
- tigrcorn_runtime/server/__init__.py +9 -0
- tigrcorn_runtime/server/app_loader.py +42 -0
- tigrcorn_runtime/server/bootstrap.py +225 -0
- tigrcorn_runtime/server/hooks.py +24 -0
- tigrcorn_runtime/server/reloader.py +109 -0
- tigrcorn_runtime/server/runner.py +878 -0
- tigrcorn_runtime/server/shutdown.py +12 -0
- tigrcorn_runtime/server/signals.py +33 -0
- tigrcorn_runtime/server/state.py +13 -0
- tigrcorn_runtime/server/supervisor.py +110 -0
- tigrcorn_runtime/workers/__init__.py +6 -0
- tigrcorn_runtime/workers/local.py +36 -0
- tigrcorn_runtime/workers/model.py +9 -0
- tigrcorn_runtime/workers/process.py +120 -0
- tigrcorn_runtime/workers/supervisor.py +58 -0
- tigrcorn_runtime-0.3.16.dev5.dist-info/METADATA +245 -0
- tigrcorn_runtime-0.3.16.dev5.dist-info/RECORD +26 -0
- tigrcorn_runtime-0.3.16.dev5.dist-info/WHEEL +5 -0
- tigrcorn_runtime-0.3.16.dev5.dist-info/licenses/LICENSE +163 -0
- tigrcorn_runtime-0.3.16.dev5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"run",
|
|
5
|
+
"serve",
|
|
6
|
+
"serve_import_string",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __getattr__(name: str):
|
|
11
|
+
if name in __all__:
|
|
12
|
+
from .api import run, serve, serve_import_string
|
|
13
|
+
|
|
14
|
+
mapping = {
|
|
15
|
+
"run": run,
|
|
16
|
+
"serve": serve,
|
|
17
|
+
"serve_import_string": serve_import_string,
|
|
18
|
+
}
|
|
19
|
+
return mapping[name]
|
|
20
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
tigrcorn_runtime/api.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from tigrcorn_config.load import build_config
|
|
7
|
+
from tigrcorn_config.model import ServerConfig
|
|
8
|
+
from tigrcorn_runtime.server.app_loader import load_app
|
|
9
|
+
from tigrcorn_runtime.server.bootstrap import run_coro_with_runtime
|
|
10
|
+
from tigrcorn_runtime.server.runner import TigrCornServer
|
|
11
|
+
from tigrcorn_runtime.server.signals import install_signal_handlers
|
|
12
|
+
from tigrcorn_core.types import ASGIApp
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def serve(
|
|
16
|
+
app: ASGIApp,
|
|
17
|
+
*,
|
|
18
|
+
profile: str | None = None,
|
|
19
|
+
app_interface: str = "auto",
|
|
20
|
+
host: str = "127.0.0.1",
|
|
21
|
+
port: int = 8000,
|
|
22
|
+
uds: str | None = None,
|
|
23
|
+
transport: str = "tcp",
|
|
24
|
+
lifespan: str = "auto",
|
|
25
|
+
log_level: str = "info",
|
|
26
|
+
access_log: bool = True,
|
|
27
|
+
ssl_certfile: str | None = None,
|
|
28
|
+
ssl_keyfile: str | None = None,
|
|
29
|
+
ssl_keyfile_password: str | bytes | None = None,
|
|
30
|
+
ssl_ca_certs: str | None = None,
|
|
31
|
+
ssl_require_client_cert: bool | None = None,
|
|
32
|
+
ssl_ciphers: str | None = None,
|
|
33
|
+
ssl_crl: str | None = None,
|
|
34
|
+
http_versions: list[str] | None = None,
|
|
35
|
+
websocket: bool | None = None,
|
|
36
|
+
enable_h2c: bool = False,
|
|
37
|
+
max_body_size: int | None = None,
|
|
38
|
+
protocols: list[str] | None = None,
|
|
39
|
+
quic_secret: bytes | None = None,
|
|
40
|
+
quic_require_retry: bool | None = None,
|
|
41
|
+
pipe_mode: str = "rawframed",
|
|
42
|
+
config: ServerConfig | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Serve an ASGI application until shutdown is requested.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
app: ASGI application callable to run.
|
|
48
|
+
profile: Optional packaged profile name.
|
|
49
|
+
app_interface: Application interface selection mode.
|
|
50
|
+
host: TCP host when the TCP transport is active.
|
|
51
|
+
port: TCP port when the TCP transport is active.
|
|
52
|
+
uds: Unix-domain socket path when the Unix transport is active.
|
|
53
|
+
transport: Listener transport family.
|
|
54
|
+
lifespan: Lifespan negotiation policy.
|
|
55
|
+
log_level: Runtime logging level.
|
|
56
|
+
access_log: Whether access logging is enabled.
|
|
57
|
+
ssl_certfile: Server certificate file path.
|
|
58
|
+
ssl_keyfile: Server private key file path.
|
|
59
|
+
ssl_keyfile_password: Optional private key password.
|
|
60
|
+
ssl_ca_certs: Optional trust-anchor bundle for peer validation.
|
|
61
|
+
ssl_require_client_cert: Whether client certificates are required.
|
|
62
|
+
ssl_ciphers: Optional TLS cipher policy.
|
|
63
|
+
ssl_crl: Optional certificate revocation list file.
|
|
64
|
+
http_versions: Enabled HTTP protocol versions.
|
|
65
|
+
websocket: Whether WebSocket handling is enabled.
|
|
66
|
+
enable_h2c: Whether cleartext HTTP/2 upgrade is enabled.
|
|
67
|
+
max_body_size: Optional request-body size cap.
|
|
68
|
+
protocols: Enabled runtime protocol families.
|
|
69
|
+
quic_secret: Optional QUIC retry/integrity secret.
|
|
70
|
+
quic_require_retry: Whether QUIC retry is required.
|
|
71
|
+
pipe_mode: Pipe listener framing mode.
|
|
72
|
+
config: Prebuilt server configuration. Other options are used only
|
|
73
|
+
when this is not supplied.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
None.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if config is None:
|
|
80
|
+
config = build_config(
|
|
81
|
+
profile=profile,
|
|
82
|
+
app_interface=app_interface,
|
|
83
|
+
host=host,
|
|
84
|
+
port=port,
|
|
85
|
+
uds=uds,
|
|
86
|
+
transport=transport,
|
|
87
|
+
lifespan=lifespan,
|
|
88
|
+
log_level=log_level,
|
|
89
|
+
access_log=access_log,
|
|
90
|
+
ssl_certfile=ssl_certfile,
|
|
91
|
+
ssl_keyfile=ssl_keyfile,
|
|
92
|
+
ssl_keyfile_password=ssl_keyfile_password,
|
|
93
|
+
ssl_ca_certs=ssl_ca_certs,
|
|
94
|
+
ssl_require_client_cert=ssl_require_client_cert,
|
|
95
|
+
ssl_ciphers=ssl_ciphers,
|
|
96
|
+
ssl_crl=ssl_crl,
|
|
97
|
+
http_versions=http_versions,
|
|
98
|
+
websocket=websocket,
|
|
99
|
+
enable_h2c=enable_h2c,
|
|
100
|
+
max_body_size=max_body_size,
|
|
101
|
+
protocols=protocols,
|
|
102
|
+
quic_secret=quic_secret,
|
|
103
|
+
quic_require_retry=quic_require_retry,
|
|
104
|
+
pipe_mode=pipe_mode,
|
|
105
|
+
)
|
|
106
|
+
server = TigrCornServer(app=app, config=config)
|
|
107
|
+
install_signal_handlers(asyncio.get_running_loop(), server.request_shutdown)
|
|
108
|
+
await server.serve_forever()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def serve_import_string(
|
|
112
|
+
app_target: str | None = None,
|
|
113
|
+
*,
|
|
114
|
+
profile: str | None = None,
|
|
115
|
+
app_interface: str = "auto",
|
|
116
|
+
host: str = "127.0.0.1",
|
|
117
|
+
port: int = 8000,
|
|
118
|
+
uds: str | None = None,
|
|
119
|
+
transport: str = "tcp",
|
|
120
|
+
lifespan: str = "auto",
|
|
121
|
+
log_level: str = "info",
|
|
122
|
+
access_log: bool = True,
|
|
123
|
+
ssl_certfile: str | None = None,
|
|
124
|
+
ssl_keyfile: str | None = None,
|
|
125
|
+
ssl_keyfile_password: str | bytes | None = None,
|
|
126
|
+
ssl_ca_certs: str | None = None,
|
|
127
|
+
ssl_require_client_cert: bool | None = None,
|
|
128
|
+
ssl_ciphers: str | None = None,
|
|
129
|
+
ssl_crl: str | None = None,
|
|
130
|
+
http_versions: list[str] | None = None,
|
|
131
|
+
websocket: bool | None = None,
|
|
132
|
+
enable_h2c: bool = False,
|
|
133
|
+
max_body_size: int | None = None,
|
|
134
|
+
protocols: list[str] | None = None,
|
|
135
|
+
quic_secret: bytes | None = None,
|
|
136
|
+
quic_require_retry: bool | None = None,
|
|
137
|
+
pipe_mode: str = "rawframed",
|
|
138
|
+
factory: bool = False,
|
|
139
|
+
config: ServerConfig | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Load an ASGI application by import string and serve it.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
app_target: Import target such as ``module:app``.
|
|
145
|
+
profile: Optional packaged profile name.
|
|
146
|
+
app_interface: Application interface selection mode.
|
|
147
|
+
host: TCP host when the TCP transport is active.
|
|
148
|
+
port: TCP port when the TCP transport is active.
|
|
149
|
+
uds: Unix-domain socket path when the Unix transport is active.
|
|
150
|
+
transport: Listener transport family.
|
|
151
|
+
lifespan: Lifespan negotiation policy.
|
|
152
|
+
log_level: Runtime logging level.
|
|
153
|
+
access_log: Whether access logging is enabled.
|
|
154
|
+
ssl_certfile: Server certificate file path.
|
|
155
|
+
ssl_keyfile: Server private key file path.
|
|
156
|
+
ssl_keyfile_password: Optional private key password.
|
|
157
|
+
ssl_ca_certs: Optional trust-anchor bundle for peer validation.
|
|
158
|
+
ssl_require_client_cert: Whether client certificates are required.
|
|
159
|
+
ssl_ciphers: Optional TLS cipher policy.
|
|
160
|
+
ssl_crl: Optional certificate revocation list file.
|
|
161
|
+
http_versions: Enabled HTTP protocol versions.
|
|
162
|
+
websocket: Whether WebSocket handling is enabled.
|
|
163
|
+
enable_h2c: Whether cleartext HTTP/2 upgrade is enabled.
|
|
164
|
+
max_body_size: Optional request-body size cap.
|
|
165
|
+
protocols: Enabled runtime protocol families.
|
|
166
|
+
quic_secret: Optional QUIC retry/integrity secret.
|
|
167
|
+
quic_require_retry: Whether QUIC retry is required.
|
|
168
|
+
pipe_mode: Pipe listener framing mode.
|
|
169
|
+
factory: Whether the import target is an application factory.
|
|
170
|
+
config: Prebuilt server configuration. Its application target is used
|
|
171
|
+
when ``app_target`` is not supplied.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
None.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: If no import target is supplied by arguments or config.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
if config is not None:
|
|
181
|
+
app_target = app_target or config.app.target
|
|
182
|
+
factory = config.app.factory if factory is False else factory
|
|
183
|
+
if app_target is None:
|
|
184
|
+
raise ValueError("app_target is required when config.app.target is not set")
|
|
185
|
+
app_dir = config.app.app_dir if config is not None else None
|
|
186
|
+
if app_dir is None:
|
|
187
|
+
app = load_app(app_target, factory=factory)
|
|
188
|
+
else:
|
|
189
|
+
app = load_app(app_target, factory=factory, app_dir=app_dir)
|
|
190
|
+
await serve(
|
|
191
|
+
cast(ASGIApp, app),
|
|
192
|
+
profile=profile,
|
|
193
|
+
app_interface=app_interface,
|
|
194
|
+
host=host,
|
|
195
|
+
port=port,
|
|
196
|
+
uds=uds,
|
|
197
|
+
transport=transport,
|
|
198
|
+
lifespan=lifespan,
|
|
199
|
+
log_level=log_level,
|
|
200
|
+
access_log=access_log,
|
|
201
|
+
ssl_certfile=ssl_certfile,
|
|
202
|
+
ssl_keyfile=ssl_keyfile,
|
|
203
|
+
ssl_keyfile_password=ssl_keyfile_password,
|
|
204
|
+
ssl_ca_certs=ssl_ca_certs,
|
|
205
|
+
ssl_require_client_cert=ssl_require_client_cert,
|
|
206
|
+
ssl_ciphers=ssl_ciphers,
|
|
207
|
+
ssl_crl=ssl_crl,
|
|
208
|
+
http_versions=http_versions,
|
|
209
|
+
websocket=websocket,
|
|
210
|
+
enable_h2c=enable_h2c,
|
|
211
|
+
max_body_size=max_body_size,
|
|
212
|
+
protocols=protocols,
|
|
213
|
+
quic_secret=quic_secret,
|
|
214
|
+
quic_require_retry=quic_require_retry,
|
|
215
|
+
pipe_mode=pipe_mode,
|
|
216
|
+
config=config,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def run(
|
|
221
|
+
app: ASGIApp | str,
|
|
222
|
+
*,
|
|
223
|
+
profile: str | None = None,
|
|
224
|
+
app_interface: str = "auto",
|
|
225
|
+
host: str = "127.0.0.1",
|
|
226
|
+
port: int = 8000,
|
|
227
|
+
uds: str | None = None,
|
|
228
|
+
transport: str = "tcp",
|
|
229
|
+
lifespan: str = "auto",
|
|
230
|
+
log_level: str = "info",
|
|
231
|
+
access_log: bool = True,
|
|
232
|
+
ssl_certfile: str | None = None,
|
|
233
|
+
ssl_keyfile: str | None = None,
|
|
234
|
+
ssl_keyfile_password: str | bytes | None = None,
|
|
235
|
+
ssl_ca_certs: str | None = None,
|
|
236
|
+
ssl_require_client_cert: bool | None = None,
|
|
237
|
+
ssl_ciphers: str | None = None,
|
|
238
|
+
ssl_crl: str | None = None,
|
|
239
|
+
http_versions: list[str] | None = None,
|
|
240
|
+
websocket: bool | None = None,
|
|
241
|
+
enable_h2c: bool = False,
|
|
242
|
+
max_body_size: int | None = None,
|
|
243
|
+
protocols: list[str] | None = None,
|
|
244
|
+
quic_secret: bytes | None = None,
|
|
245
|
+
quic_require_retry: bool | None = None,
|
|
246
|
+
pipe_mode: str = "rawframed",
|
|
247
|
+
factory: bool = False,
|
|
248
|
+
config: ServerConfig | None = None,
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Run an ASGI application or import target from synchronous code.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
app: ASGI application callable or import target.
|
|
254
|
+
profile: Optional packaged profile name.
|
|
255
|
+
app_interface: Application interface selection mode.
|
|
256
|
+
host: TCP host when the TCP transport is active.
|
|
257
|
+
port: TCP port when the TCP transport is active.
|
|
258
|
+
uds: Unix-domain socket path when the Unix transport is active.
|
|
259
|
+
transport: Listener transport family.
|
|
260
|
+
lifespan: Lifespan negotiation policy.
|
|
261
|
+
log_level: Runtime logging level.
|
|
262
|
+
access_log: Whether access logging is enabled.
|
|
263
|
+
ssl_certfile: Server certificate file path.
|
|
264
|
+
ssl_keyfile: Server private key file path.
|
|
265
|
+
ssl_keyfile_password: Optional private key password.
|
|
266
|
+
ssl_ca_certs: Optional trust-anchor bundle for peer validation.
|
|
267
|
+
ssl_require_client_cert: Whether client certificates are required.
|
|
268
|
+
ssl_ciphers: Optional TLS cipher policy.
|
|
269
|
+
ssl_crl: Optional certificate revocation list file.
|
|
270
|
+
http_versions: Enabled HTTP protocol versions.
|
|
271
|
+
websocket: Whether WebSocket handling is enabled.
|
|
272
|
+
enable_h2c: Whether cleartext HTTP/2 upgrade is enabled.
|
|
273
|
+
max_body_size: Optional request-body size cap.
|
|
274
|
+
protocols: Enabled runtime protocol families.
|
|
275
|
+
quic_secret: Optional QUIC retry/integrity secret.
|
|
276
|
+
quic_require_retry: Whether QUIC retry is required.
|
|
277
|
+
pipe_mode: Pipe listener framing mode.
|
|
278
|
+
factory: Whether a string import target is an application factory.
|
|
279
|
+
config: Prebuilt server configuration. Its process runtime selects
|
|
280
|
+
the async backend when supplied.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
None.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
runtime = config.process.runtime if config is not None else 'auto'
|
|
287
|
+
if isinstance(app, str):
|
|
288
|
+
run_coro_with_runtime(
|
|
289
|
+
lambda: serve_import_string(
|
|
290
|
+
app,
|
|
291
|
+
profile=profile,
|
|
292
|
+
app_interface=app_interface,
|
|
293
|
+
host=host,
|
|
294
|
+
port=port,
|
|
295
|
+
uds=uds,
|
|
296
|
+
transport=transport,
|
|
297
|
+
lifespan=lifespan,
|
|
298
|
+
log_level=log_level,
|
|
299
|
+
access_log=access_log,
|
|
300
|
+
ssl_certfile=ssl_certfile,
|
|
301
|
+
ssl_keyfile=ssl_keyfile,
|
|
302
|
+
ssl_keyfile_password=ssl_keyfile_password,
|
|
303
|
+
ssl_ca_certs=ssl_ca_certs,
|
|
304
|
+
ssl_require_client_cert=ssl_require_client_cert,
|
|
305
|
+
ssl_ciphers=ssl_ciphers,
|
|
306
|
+
ssl_crl=ssl_crl,
|
|
307
|
+
http_versions=http_versions,
|
|
308
|
+
websocket=websocket,
|
|
309
|
+
enable_h2c=enable_h2c,
|
|
310
|
+
max_body_size=max_body_size,
|
|
311
|
+
protocols=protocols,
|
|
312
|
+
quic_secret=quic_secret,
|
|
313
|
+
quic_require_retry=quic_require_retry,
|
|
314
|
+
pipe_mode=pipe_mode,
|
|
315
|
+
factory=factory,
|
|
316
|
+
config=config,
|
|
317
|
+
),
|
|
318
|
+
runtime=runtime,
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
run_coro_with_runtime(
|
|
322
|
+
lambda: serve(
|
|
323
|
+
app,
|
|
324
|
+
profile=profile,
|
|
325
|
+
app_interface=app_interface,
|
|
326
|
+
host=host,
|
|
327
|
+
port=port,
|
|
328
|
+
uds=uds,
|
|
329
|
+
transport=transport,
|
|
330
|
+
lifespan=lifespan,
|
|
331
|
+
log_level=log_level,
|
|
332
|
+
access_log=access_log,
|
|
333
|
+
ssl_certfile=ssl_certfile,
|
|
334
|
+
ssl_keyfile=ssl_keyfile,
|
|
335
|
+
ssl_keyfile_password=ssl_keyfile_password,
|
|
336
|
+
ssl_ca_certs=ssl_ca_certs,
|
|
337
|
+
ssl_require_client_cert=ssl_require_client_cert,
|
|
338
|
+
ssl_ciphers=ssl_ciphers,
|
|
339
|
+
ssl_crl=ssl_crl,
|
|
340
|
+
http_versions=http_versions,
|
|
341
|
+
websocket=websocket,
|
|
342
|
+
enable_h2c=enable_h2c,
|
|
343
|
+
max_body_size=max_body_size,
|
|
344
|
+
protocols=protocols,
|
|
345
|
+
quic_secret=quic_secret,
|
|
346
|
+
quic_require_retry=quic_require_retry,
|
|
347
|
+
pipe_mode=pipe_mode,
|
|
348
|
+
config=config,
|
|
349
|
+
),
|
|
350
|
+
runtime=runtime,
|
|
351
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Awaitable, Callable, Literal
|
|
6
|
+
|
|
7
|
+
from tigrcorn_compat.asgi3 import assert_asgi3_app, describe_app
|
|
8
|
+
from tigrcorn_core.types import ASGIApp, Message, Scope
|
|
9
|
+
|
|
10
|
+
AppInterface = Literal["auto", "tigr-asgi-contract", "asgi3"]
|
|
11
|
+
APP_INTERFACE_VALUES: tuple[AppInterface, ...] = ("auto", "tigr-asgi-contract", "asgi3")
|
|
12
|
+
|
|
13
|
+
Receive = Callable[[], Awaitable[Message]]
|
|
14
|
+
Send = Callable[[Message], Awaitable[None]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AppInterfaceError(TypeError):
|
|
18
|
+
"""Raised when an app cannot be safely bound to the selected interface."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class NativeContractApp:
|
|
23
|
+
app: Any
|
|
24
|
+
capabilities: tuple[str, ...] = ()
|
|
25
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
26
|
+
interface: AppInterface = "tigr-asgi-contract"
|
|
27
|
+
|
|
28
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
29
|
+
dispatcher = getattr(self.app, "dispatch", None) or getattr(self.app, "handle", None) or self.app
|
|
30
|
+
result = dispatcher(scope, receive, send)
|
|
31
|
+
if inspect.isawaitable(result):
|
|
32
|
+
await result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class DispatchSelection:
|
|
37
|
+
interface: Literal["tigr-asgi-contract", "asgi3"]
|
|
38
|
+
app: ASGIApp
|
|
39
|
+
native: bool
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def native_contract_app(
|
|
43
|
+
app: Any,
|
|
44
|
+
*,
|
|
45
|
+
capabilities: list[str] | tuple[str, ...] | None = None,
|
|
46
|
+
metadata: dict[str, Any] | None = None,
|
|
47
|
+
) -> NativeContractApp:
|
|
48
|
+
return NativeContractApp(
|
|
49
|
+
app=app,
|
|
50
|
+
capabilities=tuple(capabilities or ()),
|
|
51
|
+
metadata=dict(metadata or {}),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def mark_native_contract_app(
|
|
56
|
+
app: Any,
|
|
57
|
+
*,
|
|
58
|
+
capabilities: list[str] | tuple[str, ...] | None = None,
|
|
59
|
+
metadata: dict[str, Any] | None = None,
|
|
60
|
+
) -> Any:
|
|
61
|
+
setattr(app, "__tigrcorn_app_interface__", "tigr-asgi-contract")
|
|
62
|
+
setattr(app, "__tigrcorn_contract_capabilities__", tuple(capabilities or ()))
|
|
63
|
+
setattr(app, "__tigrcorn_contract_metadata__", dict(metadata or {}))
|
|
64
|
+
return app
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_native_contract_app(app: Any) -> bool:
|
|
68
|
+
return isinstance(app, NativeContractApp) or getattr(app, "__tigrcorn_app_interface__", None) == "tigr-asgi-contract"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _as_native(app: Any) -> NativeContractApp:
|
|
72
|
+
if isinstance(app, NativeContractApp):
|
|
73
|
+
return app
|
|
74
|
+
capabilities = getattr(app, "__tigrcorn_contract_capabilities__", ())
|
|
75
|
+
metadata = getattr(app, "__tigrcorn_contract_metadata__", {})
|
|
76
|
+
return native_contract_app(app, capabilities=capabilities, metadata=metadata)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _is_unambiguous_asgi3(app: Any) -> bool:
|
|
80
|
+
if not callable(app):
|
|
81
|
+
return False
|
|
82
|
+
signature = describe_app(app)
|
|
83
|
+
return signature.parameter_count == 3
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def resolve_app_dispatch(app: Any, interface: AppInterface = "auto") -> DispatchSelection:
|
|
87
|
+
if interface not in APP_INTERFACE_VALUES:
|
|
88
|
+
raise AppInterfaceError(f"unsupported app interface: {interface!r}")
|
|
89
|
+
if interface == "tigr-asgi-contract":
|
|
90
|
+
if not is_native_contract_app(app):
|
|
91
|
+
raise AppInterfaceError("explicit tigr-asgi-contract selection requires a native contract app marker or wrapper")
|
|
92
|
+
return DispatchSelection("tigr-asgi-contract", _as_native(app), True)
|
|
93
|
+
if interface == "asgi3":
|
|
94
|
+
try:
|
|
95
|
+
assert_asgi3_app(app)
|
|
96
|
+
except Exception as exc:
|
|
97
|
+
raise AppInterfaceError("explicit asgi3 selection requires an ASGI 3 callable") from exc
|
|
98
|
+
return DispatchSelection("asgi3", app, False)
|
|
99
|
+
|
|
100
|
+
if is_native_contract_app(app):
|
|
101
|
+
return DispatchSelection("tigr-asgi-contract", _as_native(app), True)
|
|
102
|
+
if _is_unambiguous_asgi3(app):
|
|
103
|
+
assert_asgi3_app(app)
|
|
104
|
+
return DispatchSelection("asgi3", app, False)
|
|
105
|
+
raise AppInterfaceError("ambiguous or unsupported application interface; select asgi3 or tigr-asgi-contract explicitly")
|