pywire 0.1.0__py3-none-any.whl → 0.1.1__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.
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/METADATA +23 -1
- pywire-0.1.1.dist-info/RECORD +9 -0
- pywire/__init__.py +0 -2
- pywire/cli/__init__.py +0 -1
- pywire/cli/generators.py +0 -48
- pywire/cli/main.py +0 -309
- pywire/cli/tui.py +0 -563
- pywire/cli/validate.py +0 -26
- pywire/client/.prettierignore +0 -8
- pywire/client/.prettierrc +0 -7
- pywire/client/build.mjs +0 -73
- pywire/client/eslint.config.js +0 -46
- pywire/client/package.json +0 -39
- pywire/client/pnpm-lock.yaml +0 -2971
- pywire/client/src/core/app.ts +0 -263
- pywire/client/src/core/dom-updater.test.ts +0 -78
- pywire/client/src/core/dom-updater.ts +0 -321
- pywire/client/src/core/index.ts +0 -5
- pywire/client/src/core/transport-manager.test.ts +0 -179
- pywire/client/src/core/transport-manager.ts +0 -159
- pywire/client/src/core/transports/base.ts +0 -122
- pywire/client/src/core/transports/http.ts +0 -142
- pywire/client/src/core/transports/index.ts +0 -13
- pywire/client/src/core/transports/websocket.ts +0 -97
- pywire/client/src/core/transports/webtransport.ts +0 -149
- pywire/client/src/dev/dev-app.ts +0 -93
- pywire/client/src/dev/error-trace.test.ts +0 -97
- pywire/client/src/dev/error-trace.ts +0 -76
- pywire/client/src/dev/index.ts +0 -4
- pywire/client/src/dev/status-overlay.ts +0 -63
- pywire/client/src/events/handler.test.ts +0 -318
- pywire/client/src/events/handler.ts +0 -454
- pywire/client/src/pywire.core.ts +0 -22
- pywire/client/src/pywire.dev.ts +0 -27
- pywire/client/tsconfig.json +0 -17
- pywire/client/vitest.config.ts +0 -15
- pywire/compiler/__init__.py +0 -6
- pywire/compiler/ast_nodes.py +0 -304
- pywire/compiler/attributes/__init__.py +0 -6
- pywire/compiler/attributes/base.py +0 -24
- pywire/compiler/attributes/conditional.py +0 -37
- pywire/compiler/attributes/events.py +0 -55
- pywire/compiler/attributes/form.py +0 -37
- pywire/compiler/attributes/loop.py +0 -75
- pywire/compiler/attributes/reactive.py +0 -34
- pywire/compiler/build.py +0 -28
- pywire/compiler/build_artifacts.py +0 -342
- pywire/compiler/codegen/__init__.py +0 -5
- pywire/compiler/codegen/attributes/__init__.py +0 -6
- pywire/compiler/codegen/attributes/base.py +0 -19
- pywire/compiler/codegen/attributes/events.py +0 -35
- pywire/compiler/codegen/directives/__init__.py +0 -6
- pywire/compiler/codegen/directives/base.py +0 -16
- pywire/compiler/codegen/directives/path.py +0 -53
- pywire/compiler/codegen/generator.py +0 -2341
- pywire/compiler/codegen/template.py +0 -2178
- pywire/compiler/directives/__init__.py +0 -7
- pywire/compiler/directives/base.py +0 -20
- pywire/compiler/directives/component.py +0 -33
- pywire/compiler/directives/context.py +0 -93
- pywire/compiler/directives/layout.py +0 -49
- pywire/compiler/directives/no_spa.py +0 -24
- pywire/compiler/directives/path.py +0 -71
- pywire/compiler/directives/props.py +0 -88
- pywire/compiler/exceptions.py +0 -19
- pywire/compiler/interpolation/__init__.py +0 -6
- pywire/compiler/interpolation/base.py +0 -28
- pywire/compiler/interpolation/jinja.py +0 -272
- pywire/compiler/parser.py +0 -750
- pywire/compiler/paths.py +0 -29
- pywire/compiler/preprocessor.py +0 -43
- pywire/core/wire.py +0 -119
- pywire/py.typed +0 -0
- pywire/runtime/__init__.py +0 -7
- pywire/runtime/aioquic_server.py +0 -194
- pywire/runtime/app.py +0 -889
- pywire/runtime/compile_error_page.py +0 -195
- pywire/runtime/debug.py +0 -203
- pywire/runtime/dev_server.py +0 -434
- pywire/runtime/dev_server.py.broken +0 -268
- pywire/runtime/error_page.py +0 -64
- pywire/runtime/error_renderer.py +0 -23
- pywire/runtime/escape.py +0 -23
- pywire/runtime/files.py +0 -40
- pywire/runtime/helpers.py +0 -97
- pywire/runtime/http_transport.py +0 -253
- pywire/runtime/loader.py +0 -272
- pywire/runtime/logging.py +0 -72
- pywire/runtime/page.py +0 -384
- pywire/runtime/pydantic_integration.py +0 -52
- pywire/runtime/router.py +0 -229
- pywire/runtime/server.py +0 -25
- pywire/runtime/style_collector.py +0 -31
- pywire/runtime/upload_manager.py +0 -76
- pywire/runtime/validation.py +0 -449
- pywire/runtime/websocket.py +0 -665
- pywire/runtime/webtransport_handler.py +0 -195
- pywire-0.1.0.dist-info/RECORD +0 -104
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/WHEEL +0 -0
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/entry_points.txt +0 -0
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/licenses/LICENSE +0 -0
pywire/compiler/paths.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""Helpers for PyWire filesystem paths."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def ensure_pywire_folder() -> Path:
|
|
9
|
-
"""Ensure .pywire exists and has a local .gitignore."""
|
|
10
|
-
dot_pywire = Path(".pywire")
|
|
11
|
-
if not dot_pywire.exists():
|
|
12
|
-
dot_pywire.mkdir()
|
|
13
|
-
|
|
14
|
-
gitignore_path = dot_pywire / ".gitignore"
|
|
15
|
-
if not gitignore_path.exists():
|
|
16
|
-
gitignore_path.write_text("*")
|
|
17
|
-
|
|
18
|
-
return dot_pywire
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_pywire_path(*parts: str) -> Path:
|
|
22
|
-
"""Return a path inside .pywire/."""
|
|
23
|
-
dot_pywire = ensure_pywire_folder()
|
|
24
|
-
return dot_pywire.joinpath(*parts)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def get_build_path(*parts: str) -> Path:
|
|
28
|
-
"""Return a path inside .pywire/build/."""
|
|
29
|
-
return get_pywire_path("build", *parts)
|
pywire/compiler/preprocessor.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def preprocess_python_code(code: str) -> str:
|
|
5
|
-
"""
|
|
6
|
-
Pre-process Python code to handle custom PyWire syntax ($var -> var.value).
|
|
7
|
-
|
|
8
|
-
This function replaces usage of the '$' prefix on identifiers with the '.value' suffix access,
|
|
9
|
-
which is the standard way to access PyWire 'wire' primitives.
|
|
10
|
-
|
|
11
|
-
Example:
|
|
12
|
-
$count += 1 -> count.value += 1
|
|
13
|
-
|
|
14
|
-
It respects Python string boundaries (single and triple quoted) to avoid replacing
|
|
15
|
-
text inside strings.
|
|
16
|
-
"""
|
|
17
|
-
# Pattern matches:
|
|
18
|
-
# Group 1: Strings (Triple double, Triple single, Double, Single)
|
|
19
|
-
# Group 2: The $ token
|
|
20
|
-
# Group 3: The identifier
|
|
21
|
-
|
|
22
|
-
# We use non-capturing groups (?:...) for internal parts of string patterns to verify content
|
|
23
|
-
# Triple quoted strings can contain newlines ([\s\S]*?)
|
|
24
|
-
# Single quoted strings cannot contain unescaped newlines (not matching \n)
|
|
25
|
-
|
|
26
|
-
pattern = (
|
|
27
|
-
r"(\"\"\"[\s\S]*?\"\"\"|'''[\s\S]*?'''|" # Triple quoted strings
|
|
28
|
-
r"\"(?:\\.|[^\\\"\n])*\"|'(?:\\.|[^\\'\n])*')|" # Single quoted strings
|
|
29
|
-
r"(\$)([a-zA-Z_]\w*)" # The syntax we want to replace
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
def replacer(match):
|
|
33
|
-
# If it matched a string (Group 1), return it unchanged
|
|
34
|
-
if match.group(1):
|
|
35
|
-
return match.group(1)
|
|
36
|
-
|
|
37
|
-
# If it matched our syntax ($ + Identifier)
|
|
38
|
-
if match.group(2) and match.group(3):
|
|
39
|
-
return f"{match.group(3)}.value"
|
|
40
|
-
|
|
41
|
-
return match.group(0)
|
|
42
|
-
|
|
43
|
-
return re.sub(pattern, replacer, code, flags=re.MULTILINE)
|
pywire/core/wire.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
from contextvars import ContextVar
|
|
2
|
-
from typing import TypeVar, Generic, Any, Optional, Tuple, cast
|
|
3
|
-
from weakref import WeakSet
|
|
4
|
-
|
|
5
|
-
T = TypeVar("T")
|
|
6
|
-
|
|
7
|
-
_render_context: ContextVar[Optional[Tuple[Any, str]]] = ContextVar(
|
|
8
|
-
"pywire_render_context", default=None
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def set_render_context(page: Any, region_id: str) -> Any:
|
|
13
|
-
return _render_context.set((page, region_id))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def reset_render_context(token: Any) -> None:
|
|
17
|
-
_render_context.reset(token)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class wire(Generic[T]):
|
|
21
|
-
"""
|
|
22
|
-
A reactive container for state.
|
|
23
|
-
|
|
24
|
-
Usage:
|
|
25
|
-
# Single value
|
|
26
|
-
count = wire(0)
|
|
27
|
-
count.value += 1
|
|
28
|
-
|
|
29
|
-
# Namespace
|
|
30
|
-
user = wire(name="Alice", age=30)
|
|
31
|
-
user.name = "Bob"
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
def __init__(self, value: Optional[T] = None, **kwargs):
|
|
35
|
-
# We use strict dict manipulation to avoid triggering __setattr__
|
|
36
|
-
self.__dict__["_value"] = value
|
|
37
|
-
self.__dict__["_namespace"] = kwargs
|
|
38
|
-
self.__dict__["_pages"] = WeakSet()
|
|
39
|
-
|
|
40
|
-
def _track_read(self, field: str) -> None:
|
|
41
|
-
ctx = _render_context.get()
|
|
42
|
-
if not ctx:
|
|
43
|
-
return
|
|
44
|
-
page, region_id = ctx
|
|
45
|
-
pages = cast(WeakSet[Any], self.__dict__.get("_pages"))
|
|
46
|
-
if pages is not None:
|
|
47
|
-
pages.add(page)
|
|
48
|
-
register = getattr(page, "_register_wire_read", None)
|
|
49
|
-
if register:
|
|
50
|
-
register(self, field, region_id)
|
|
51
|
-
|
|
52
|
-
def _notify_write(self, field: str) -> None:
|
|
53
|
-
pages = cast(WeakSet[Any], self.__dict__.get("_pages"))
|
|
54
|
-
if not pages:
|
|
55
|
-
return
|
|
56
|
-
for page in list(pages):
|
|
57
|
-
invalidate = getattr(page, "_invalidate_wire", None)
|
|
58
|
-
if invalidate:
|
|
59
|
-
invalidate(self, field)
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def value(self) -> T:
|
|
63
|
-
"""Access the underlying value."""
|
|
64
|
-
# strict priority: if 'value' is in namespace, return that.
|
|
65
|
-
# otherwise return the positional value.
|
|
66
|
-
if "value" in self.__dict__["_namespace"]:
|
|
67
|
-
self._track_read("value")
|
|
68
|
-
return self.__dict__["_namespace"]["value"]
|
|
69
|
-
self._track_read("value")
|
|
70
|
-
return self.__dict__["_value"]
|
|
71
|
-
|
|
72
|
-
@value.setter
|
|
73
|
-
def value(self, new_val: T):
|
|
74
|
-
if "value" in self.__dict__["_namespace"]:
|
|
75
|
-
self.__dict__["_namespace"]["value"] = new_val
|
|
76
|
-
else:
|
|
77
|
-
self.__dict__["_value"] = new_val
|
|
78
|
-
self._notify_write("value")
|
|
79
|
-
|
|
80
|
-
# Alias for shorter typing, if desired.
|
|
81
|
-
@property
|
|
82
|
-
def val(self) -> T:
|
|
83
|
-
return self.value
|
|
84
|
-
|
|
85
|
-
@val.setter
|
|
86
|
-
def val(self, new_val: T):
|
|
87
|
-
self.value = new_val
|
|
88
|
-
|
|
89
|
-
def __getattr__(self, name: str) -> Any:
|
|
90
|
-
if name in self.__dict__["_namespace"]:
|
|
91
|
-
self._track_read(name)
|
|
92
|
-
return self.__dict__["_namespace"][name]
|
|
93
|
-
raise AttributeError(f"'wire' object has no attribute '{name}'")
|
|
94
|
-
|
|
95
|
-
def __setattr__(self, name: str, val: Any):
|
|
96
|
-
# If the attribute is 'value' or 'val', go through the property
|
|
97
|
-
if name in ("value", "val"):
|
|
98
|
-
super().__setattr__(name, val)
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
# If it's an internal attribute (shouldn't really happen from outside)
|
|
102
|
-
if name in self.__dict__:
|
|
103
|
-
super().__setattr__(name, val)
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
# Otherwise, treat it as setting a namespace key
|
|
107
|
-
# We allow adding new keys dynamically
|
|
108
|
-
self.__dict__["_namespace"][name] = val
|
|
109
|
-
self._notify_write(name)
|
|
110
|
-
|
|
111
|
-
def __repr__(self):
|
|
112
|
-
if self._namespace:
|
|
113
|
-
items = [f"{k}={v!r}" for k, v in self._namespace.items()]
|
|
114
|
-
# If we also have a positional value that isn't None (and not shadowed), show it?
|
|
115
|
-
# Typically one uses EITHER positional OR kwargs.
|
|
116
|
-
if self._value is not None and "value" not in self._namespace:
|
|
117
|
-
return f"wire({self._value!r}, {', '.join(items)})"
|
|
118
|
-
return f"wire({', '.join(items)})"
|
|
119
|
-
return f"wire({self._value!r})"
|
pywire/py.typed
DELETED
|
File without changes
|
pywire/runtime/__init__.py
DELETED
pywire/runtime/aioquic_server.py
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ASGI HTTP/3 + WebTransport server using aioquic directly.
|
|
3
|
-
|
|
4
|
-
This module bypasses Hypercorn to use aioquic's native WebTransport support,
|
|
5
|
-
which requires explicit enable_webtransport=True in H3Connection initialization.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
from typing import Any, Callable, Optional
|
|
10
|
-
|
|
11
|
-
from aioquic.asyncio import QuicConnectionProtocol, serve
|
|
12
|
-
from aioquic.h3.connection import H3_ALPN, H3Connection
|
|
13
|
-
from aioquic.h3.events import (
|
|
14
|
-
H3Event,
|
|
15
|
-
HeadersReceived,
|
|
16
|
-
)
|
|
17
|
-
from aioquic.quic.configuration import QuicConfiguration
|
|
18
|
-
from aioquic.quic.events import ProtocolNegotiated, QuicEvent
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class ASGIProtocol(QuicConnectionProtocol):
|
|
22
|
-
"""
|
|
23
|
-
QUIC/HTTP3 protocol handler that routes to ASGI application.
|
|
24
|
-
|
|
25
|
-
Handles WebTransport by creating H3Connection with enable_webtransport=True.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
def __init__(
|
|
29
|
-
self, quic: Any, *args: Any, app_factory: Callable, **kwargs: Any
|
|
30
|
-
) -> None:
|
|
31
|
-
super().__init__(quic, *args, **kwargs)
|
|
32
|
-
self._http: Optional[H3Connection] = None
|
|
33
|
-
self._app_factory = app_factory
|
|
34
|
-
self._app: Optional[Callable] = None
|
|
35
|
-
|
|
36
|
-
def quic_event_received(self, event: QuicEvent) -> None:
|
|
37
|
-
"""Handle QUIC events, including protocol negotiation."""
|
|
38
|
-
if isinstance(event, ProtocolNegotiated):
|
|
39
|
-
if event.alpn_protocol in H3_ALPN:
|
|
40
|
-
# CRITICAL: Enable WebTransport support
|
|
41
|
-
self._http = H3Connection(self._quic, enable_webtransport=True)
|
|
42
|
-
print(
|
|
43
|
-
"PyWire: HTTP/3 connection established with WebTransport enabled",
|
|
44
|
-
flush=True,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# Pass events to HTTP/3 layer
|
|
48
|
-
if self._http is not None:
|
|
49
|
-
for http_event in self._http.handle_event(event):
|
|
50
|
-
self.http_event_received(http_event)
|
|
51
|
-
|
|
52
|
-
def http_event_received(self, event: H3Event) -> None:
|
|
53
|
-
"""Route HTTP/3 events to ASGI application."""
|
|
54
|
-
if isinstance(event, HeadersReceived):
|
|
55
|
-
# Parse ASGI scope from headers
|
|
56
|
-
scope = self._build_scope(event)
|
|
57
|
-
print(
|
|
58
|
-
f"PyWire: Received {scope['type']} request to {scope.get('path', '/')}",
|
|
59
|
-
flush=True,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
# Create ASGI handler
|
|
63
|
-
if self._app is None:
|
|
64
|
-
self._app = self._app_factory()
|
|
65
|
-
|
|
66
|
-
# Dispatch to ASGI app
|
|
67
|
-
asyncio.ensure_future(self._handle_asgi(scope, event))
|
|
68
|
-
|
|
69
|
-
def _build_scope(self, event: HeadersReceived) -> dict:
|
|
70
|
-
"""Build ASGI scope dictionary from HTTP/3 headers."""
|
|
71
|
-
headers = []
|
|
72
|
-
method = ""
|
|
73
|
-
path = "/"
|
|
74
|
-
protocol = None
|
|
75
|
-
|
|
76
|
-
for header, value in event.headers:
|
|
77
|
-
if header == b":method":
|
|
78
|
-
method = value.decode()
|
|
79
|
-
elif header == b":path":
|
|
80
|
-
path = value.decode()
|
|
81
|
-
elif header == b":protocol":
|
|
82
|
-
protocol = value.decode()
|
|
83
|
-
elif header and not header.startswith(b":"):
|
|
84
|
-
headers.append((header, value))
|
|
85
|
-
|
|
86
|
-
# Determine scope type
|
|
87
|
-
if method == "CONNECT" and protocol == "webtransport":
|
|
88
|
-
scope_type = "webtransport"
|
|
89
|
-
else:
|
|
90
|
-
scope_type = "http"
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
"type": scope_type,
|
|
94
|
-
"asgi": {"version": "3.0"},
|
|
95
|
-
"http_version": "3",
|
|
96
|
-
"method": method,
|
|
97
|
-
"path": path,
|
|
98
|
-
"headers": headers,
|
|
99
|
-
"server": ("localhost", 3000),
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async def _handle_asgi(self, scope: dict, event: HeadersReceived) -> None:
|
|
103
|
-
"""Handle ASGI application invocation."""
|
|
104
|
-
stream_id = event.stream_id
|
|
105
|
-
|
|
106
|
-
# Create receive/send callables
|
|
107
|
-
async def receive() -> dict:
|
|
108
|
-
# For WebTransport: wait for connect message
|
|
109
|
-
if scope["type"] == "webtransport":
|
|
110
|
-
return {"type": "webtransport.connect"}
|
|
111
|
-
return {"type": "http.request"}
|
|
112
|
-
|
|
113
|
-
async def send(message: dict) -> None:
|
|
114
|
-
msg_type = message["type"]
|
|
115
|
-
print(f"PyWire: Sending {msg_type} on stream {stream_id}", flush=True)
|
|
116
|
-
|
|
117
|
-
if msg_type == "webtransport.accept":
|
|
118
|
-
# Send 200 OK for WebTransport
|
|
119
|
-
if self._http:
|
|
120
|
-
self._http.send_headers(
|
|
121
|
-
stream_id=stream_id,
|
|
122
|
-
headers=[
|
|
123
|
-
(b":status", b"200"),
|
|
124
|
-
(b"sec-webtransport-http3-draft", b"draft02"),
|
|
125
|
-
],
|
|
126
|
-
)
|
|
127
|
-
print(
|
|
128
|
-
f"PyWire: WebTransport connection accepted on stream {stream_id}",
|
|
129
|
-
flush=True,
|
|
130
|
-
)
|
|
131
|
-
elif msg_type == "http.response.start":
|
|
132
|
-
status = message.get("status", 200)
|
|
133
|
-
response_headers = message.get("headers", [])
|
|
134
|
-
if self._http:
|
|
135
|
-
self._http.send_headers(
|
|
136
|
-
stream_id=stream_id,
|
|
137
|
-
headers=[(b":status", str(status).encode())] + response_headers,
|
|
138
|
-
)
|
|
139
|
-
elif msg_type == "http.response.body":
|
|
140
|
-
data = message.get("body", b"")
|
|
141
|
-
if self._http:
|
|
142
|
-
self._http.send_data(
|
|
143
|
-
stream_id=stream_id,
|
|
144
|
-
data=data,
|
|
145
|
-
end_stream=not message.get("more_body", False),
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
self.transmit()
|
|
149
|
-
|
|
150
|
-
# Dispatch to ASGI app
|
|
151
|
-
if self._app:
|
|
152
|
-
await self._app(scope, receive, send)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
async def run_aioquic_server(
|
|
156
|
-
app_factory: Callable,
|
|
157
|
-
host: str,
|
|
158
|
-
port: int,
|
|
159
|
-
certfile: str,
|
|
160
|
-
keyfile: str,
|
|
161
|
-
) -> None:
|
|
162
|
-
"""
|
|
163
|
-
Run HTTP/3 + WebTransport server using aioquic directly.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
app_factory: Callable that returns ASGI application
|
|
167
|
-
host: Host to bind to
|
|
168
|
-
port: Port to bind to
|
|
169
|
-
certfile: Path to SSL certificate
|
|
170
|
-
keyfile: Path to SSL private key
|
|
171
|
-
"""
|
|
172
|
-
# Configure QUIC
|
|
173
|
-
configuration = QuicConfiguration(
|
|
174
|
-
alpn_protocols=H3_ALPN,
|
|
175
|
-
is_client=False,
|
|
176
|
-
max_datagram_frame_size=65536,
|
|
177
|
-
)
|
|
178
|
-
configuration.load_cert_chain(certfile, keyfile)
|
|
179
|
-
|
|
180
|
-
# Create protocol factory
|
|
181
|
-
# Create protocol factory
|
|
182
|
-
def create_protocol(*args: Any, **kwargs: Any) -> ASGIProtocol:
|
|
183
|
-
if "app_factory" in kwargs:
|
|
184
|
-
del kwargs["app_factory"]
|
|
185
|
-
return ASGIProtocol(*args, app_factory=app_factory, **kwargs)
|
|
186
|
-
|
|
187
|
-
# Start server
|
|
188
|
-
print(f"PyWire: Starting aioquic HTTP/3 server on {host}:{port}", flush=True)
|
|
189
|
-
await serve(
|
|
190
|
-
host,
|
|
191
|
-
port,
|
|
192
|
-
configuration=configuration,
|
|
193
|
-
create_protocol=create_protocol,
|
|
194
|
-
)
|