omserv 0.0.0.dev7__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.
Files changed (44) hide show
  1. omserv/__about__.py +28 -0
  2. omserv/__init__.py +0 -0
  3. omserv/apps/__init__.py +0 -0
  4. omserv/apps/base.py +23 -0
  5. omserv/apps/inject.py +89 -0
  6. omserv/apps/markers.py +41 -0
  7. omserv/apps/routes.py +139 -0
  8. omserv/apps/sessions.py +57 -0
  9. omserv/apps/templates.py +90 -0
  10. omserv/dbs.py +24 -0
  11. omserv/node/__init__.py +0 -0
  12. omserv/node/models.py +53 -0
  13. omserv/node/registry.py +124 -0
  14. omserv/node/sql.py +131 -0
  15. omserv/secrets.py +12 -0
  16. omserv/server/__init__.py +18 -0
  17. omserv/server/config.py +51 -0
  18. omserv/server/debug.py +14 -0
  19. omserv/server/events.py +83 -0
  20. omserv/server/headers.py +36 -0
  21. omserv/server/lifespans.py +132 -0
  22. omserv/server/multiprocess.py +157 -0
  23. omserv/server/protocols/__init__.py +1 -0
  24. omserv/server/protocols/h11.py +334 -0
  25. omserv/server/protocols/h2.py +407 -0
  26. omserv/server/protocols/protocols.py +91 -0
  27. omserv/server/protocols/types.py +18 -0
  28. omserv/server/resources/__init__.py +8 -0
  29. omserv/server/sockets.py +111 -0
  30. omserv/server/ssl.py +47 -0
  31. omserv/server/streams/__init__.py +0 -0
  32. omserv/server/streams/httpstream.py +237 -0
  33. omserv/server/streams/utils.py +53 -0
  34. omserv/server/streams/wsstream.py +447 -0
  35. omserv/server/taskspawner.py +111 -0
  36. omserv/server/tcpserver.py +173 -0
  37. omserv/server/types.py +94 -0
  38. omserv/server/workercontext.py +52 -0
  39. omserv/server/workers.py +193 -0
  40. omserv-0.0.0.dev7.dist-info/LICENSE +21 -0
  41. omserv-0.0.0.dev7.dist-info/METADATA +21 -0
  42. omserv-0.0.0.dev7.dist-info/RECORD +44 -0
  43. omserv-0.0.0.dev7.dist-info/WHEEL +5 -0
  44. omserv-0.0.0.dev7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,173 @@
1
+ import errno
2
+ import logging
3
+ import math
4
+ import typing as ta
5
+
6
+ import anyio.abc
7
+
8
+ from omlish import check
9
+
10
+ from .config import Config
11
+ from .events import Closed
12
+ from .events import RawData
13
+ from .events import ServerEvent
14
+ from .events import Updated
15
+ from .protocols import ProtocolWrapper
16
+ from .sockets import parse_socket_addr
17
+ from .taskspawner import TaskSpawner
18
+ from .types import AppWrapper
19
+ from .workercontext import WorkerContext
20
+
21
+
22
+ log = logging.getLogger(__name__)
23
+
24
+
25
+ MAX_RECV = 2 ** 16
26
+
27
+
28
+ class TcpServer:
29
+ def __init__(
30
+ self,
31
+ app: AppWrapper,
32
+ config: Config,
33
+ context: WorkerContext,
34
+ stream: anyio.abc.SocketStream,
35
+ ) -> None:
36
+ super().__init__()
37
+
38
+ self.app = app
39
+ self.config = config
40
+ self.context = context
41
+ self.protocol: ProtocolWrapper
42
+ self.send_lock = anyio.Lock()
43
+ self.idle_lock = anyio.Lock()
44
+ self.stream = stream
45
+
46
+ self._idle_handle: anyio.abc.CancelScope | None = None
47
+ self._task_spawner: TaskSpawner | None = None
48
+
49
+ def __await__(self) -> ta.Generator[ta.Any, None, None]:
50
+ return self.run().__await__()
51
+
52
+ async def run(self) -> None:
53
+ socket = self.stream._raw_socket # noqa
54
+
55
+ try:
56
+ client = parse_socket_addr(socket.family, socket.getpeername())
57
+ server = parse_socket_addr(socket.family, socket.getsockname())
58
+
59
+ async with TaskSpawner() as task_spawner:
60
+ self._task_spawner = task_spawner
61
+
62
+ self.protocol = ProtocolWrapper(
63
+ self.app,
64
+ self.config,
65
+ self.context,
66
+ task_spawner,
67
+ client,
68
+ server,
69
+ self.protocol_send,
70
+ )
71
+
72
+ await self.protocol.initiate()
73
+ await self._start_idle()
74
+ await self._read_data()
75
+
76
+ except* OSError:
77
+ pass
78
+
79
+ except* Exception as eg:
80
+ for e in eg.exceptions:
81
+ log.exception('Internal omlicorn error', exc_info=e)
82
+
83
+ finally:
84
+ await self._close()
85
+
86
+ async def protocol_send(self, event: ServerEvent) -> None:
87
+ if isinstance(event, RawData):
88
+ async with self.send_lock:
89
+ try:
90
+ with anyio.CancelScope() as cancel_scope:
91
+ cancel_scope.shield = True
92
+ await self.stream.send(event.data)
93
+
94
+ except (anyio.BrokenResourceError, anyio.ClosedResourceError):
95
+ await self.protocol.handle(Closed())
96
+
97
+ elif isinstance(event, Closed):
98
+ await self._close()
99
+ await self.protocol.handle(Closed())
100
+
101
+ elif isinstance(event, Updated):
102
+ if event.idle:
103
+ await self._start_idle()
104
+ else:
105
+ await self._stop_idle()
106
+
107
+ async def _read_data(self) -> None:
108
+ while True:
109
+ try:
110
+ with anyio.fail_after(self.config.read_timeout or math.inf):
111
+ data = await self.stream.receive(MAX_RECV)
112
+
113
+ except (
114
+ anyio.EndOfStream,
115
+ anyio.ClosedResourceError,
116
+ anyio.BrokenResourceError,
117
+ TimeoutError,
118
+ ):
119
+ break
120
+
121
+ else:
122
+ await self.protocol.handle(RawData(data))
123
+ if data == b'':
124
+ break
125
+
126
+ await self.protocol.handle(Closed())
127
+
128
+ async def _close(self) -> None:
129
+ try:
130
+ await self.stream.send_eof()
131
+
132
+ except OSError as e:
133
+ if e.errno != errno.EBADF:
134
+ raise
135
+
136
+ except (
137
+ anyio.BrokenResourceError,
138
+ AttributeError,
139
+ anyio.BusyResourceError,
140
+ anyio.ClosedResourceError,
141
+ ):
142
+ # They're already gone, nothing to do - or it is a SSL stream
143
+ pass
144
+
145
+ await self.stream.aclose()
146
+
147
+ async def _initiate_server_close(self) -> None:
148
+ await self.protocol.handle(Closed())
149
+ await self.stream.aclose()
150
+
151
+ async def _start_idle(self) -> None:
152
+ async with self.idle_lock:
153
+ if self._idle_handle is None:
154
+ self._idle_handle = await check.not_none(self._task_spawner).start(self._run_idle)
155
+
156
+ async def _stop_idle(self) -> None:
157
+ async with self.idle_lock:
158
+ if self._idle_handle is not None:
159
+ self._idle_handle.cancel()
160
+ self._idle_handle = None
161
+
162
+ async def _run_idle(
163
+ self,
164
+ task_status: anyio.abc.TaskStatus[ta.Any] = anyio.TASK_STATUS_IGNORED,
165
+ ) -> None:
166
+ cancel_scope = anyio.CancelScope()
167
+ task_status.started(cancel_scope)
168
+ with cancel_scope:
169
+ with anyio.move_on_after(self.config.keep_alive_timeout):
170
+ await self.context.terminated.wait()
171
+
172
+ cancel_scope.shield = True
173
+ await self._initiate_server_close()
omserv/server/types.py ADDED
@@ -0,0 +1,94 @@
1
+ import typing as ta
2
+
3
+
4
+ ##
5
+
6
+
7
+ AsgiReceiveEvent: ta.TypeAlias = dict[str, ta.Any]
8
+ AsgiSendEvent: ta.TypeAlias = dict[str, ta.Any]
9
+
10
+ AsgiReceiveCallable: ta.TypeAlias = ta.Callable[[], ta.Awaitable[AsgiReceiveEvent]]
11
+ AsgiSendCallable: ta.TypeAlias = ta.Callable[[AsgiSendEvent], ta.Awaitable[None]]
12
+
13
+ Scope: ta.TypeAlias = dict[str, ta.Any]
14
+ AsgiFramework: ta.TypeAlias = ta.Callable[
15
+ [
16
+ Scope,
17
+ AsgiReceiveCallable,
18
+ AsgiSendCallable,
19
+ ],
20
+ ta.Awaitable[None],
21
+ ]
22
+
23
+ LifespanScope: ta.TypeAlias = Scope
24
+
25
+ HttpResponseStartEvent: ta.TypeAlias = dict[str, ta.Any]
26
+ HttpScope: ta.TypeAlias = Scope
27
+ WebsocketScope: ta.TypeAlias = Scope
28
+
29
+ WebsocketAcceptEvent: ta.TypeAlias = dict[str, ta.Any]
30
+ WebsocketResponseBodyEvent: ta.TypeAlias = dict[str, ta.Any]
31
+ WebsocketResponseStartEvent: ta.TypeAlias = dict[str, ta.Any]
32
+
33
+
34
+ ##
35
+
36
+
37
+ class UnexpectedMessageError(Exception):
38
+ pass
39
+
40
+
41
+ class AsgiWrapper:
42
+ def __init__(self, app: AsgiFramework) -> None:
43
+ super().__init__()
44
+ self.app = app
45
+
46
+ async def __call__(
47
+ self,
48
+ scope: Scope,
49
+ receive: AsgiReceiveCallable,
50
+ send: AsgiSendCallable,
51
+ sync_spawn: ta.Callable,
52
+ call_soon: ta.Callable,
53
+ ) -> None:
54
+ await self.app(scope, receive, send)
55
+
56
+
57
+ class AppWrapper(ta.Protocol):
58
+ async def __call__(
59
+ self,
60
+ scope: Scope,
61
+ receive: AsgiReceiveCallable,
62
+ send: AsgiSendCallable,
63
+ sync_spawn: ta.Callable,
64
+ call_soon: ta.Callable,
65
+ ) -> None:
66
+ pass
67
+
68
+
69
+ def wrap_app(
70
+ app: AsgiFramework | AppWrapper,
71
+ ) -> AppWrapper:
72
+ if isinstance(app, AsgiWrapper):
73
+ return app
74
+ return AsgiWrapper(app) # type: ignore
75
+
76
+
77
+ ##
78
+
79
+
80
+ class WaitableEvent(ta.Protocol):
81
+ def __init__(self) -> None:
82
+ pass
83
+
84
+ async def clear(self) -> None:
85
+ pass
86
+
87
+ async def set(self) -> None:
88
+ pass
89
+
90
+ async def wait(self) -> None:
91
+ pass
92
+
93
+ def is_set(self) -> bool:
94
+ pass
@@ -0,0 +1,52 @@
1
+ import anyio
2
+
3
+ from .types import WaitableEvent
4
+
5
+
6
+ class WaitableEventWrapper:
7
+ def __init__(self) -> None:
8
+ super().__init__()
9
+ self._event = anyio.Event()
10
+
11
+ async def clear(self) -> None:
12
+ self._event = anyio.Event()
13
+
14
+ async def wait(self) -> None:
15
+ await self._event.wait()
16
+
17
+ async def set(self) -> None:
18
+ self._event.set()
19
+
20
+ def is_set(self) -> bool:
21
+ return self._event.is_set()
22
+
23
+
24
+ class WorkerContext:
25
+ event_class: type[WaitableEvent] = WaitableEventWrapper
26
+
27
+ def __init__(self, max_requests: int | None) -> None:
28
+ super().__init__()
29
+ self.max_requests = max_requests
30
+ self.requests = 0
31
+ self.terminate = self.event_class()
32
+ self.terminated = self.event_class()
33
+
34
+ async def mark_request(self) -> None:
35
+ if self.max_requests is None:
36
+ return
37
+
38
+ self.requests += 1
39
+ if self.requests > self.max_requests:
40
+ await self.terminate.set()
41
+
42
+ @staticmethod
43
+ async def sleep(wait: float) -> None:
44
+ return await anyio.sleep(wait)
45
+
46
+ @staticmethod
47
+ def time() -> float:
48
+ return anyio.current_time()
49
+
50
+
51
+ class ShutdownError(Exception):
52
+ pass
@@ -0,0 +1,193 @@
1
+ import errno
2
+ import functools
3
+ import logging
4
+ import os
5
+ import random
6
+ import signal # noqa
7
+ import typing as ta
8
+
9
+ import anyio
10
+ import anyio.abc
11
+
12
+ from .config import Config
13
+ from .lifespans import Lifespan
14
+ from .sockets import Sockets
15
+ from .sockets import create_sockets
16
+ from .sockets import repr_socket_addr
17
+ from .tcpserver import TcpServer
18
+ from .types import AppWrapper
19
+ from .types import AsgiFramework
20
+ from .types import wrap_app
21
+ from .workercontext import ShutdownError
22
+ from .workercontext import WorkerContext
23
+
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+
28
+ async def raise_shutdown(shutdown_event: ta.Callable[..., ta.Awaitable]) -> None:
29
+ await shutdown_event()
30
+ raise ShutdownError
31
+
32
+
33
+ # Errors that accept(2) can return, and which indicate that the system is overloaded
34
+ ACCEPT_CAPACITY_ERRNOS = {
35
+ errno.EMFILE,
36
+ errno.ENFILE,
37
+ errno.ENOMEM,
38
+ errno.ENOBUFS,
39
+ }
40
+
41
+
42
+ async def _run_handler(
43
+ stream: anyio.abc.SocketStream,
44
+ handler: ta.Callable[[anyio.abc.SocketStream], ta.Awaitable],
45
+ ) -> None:
46
+ try:
47
+ await handler(stream)
48
+ finally:
49
+ await anyio.aclose_forcefully(stream)
50
+
51
+
52
+ SLEEP_TIME = 0.100
53
+
54
+
55
+ async def _serve_one_listener(
56
+ listener: anyio.abc.SocketListener,
57
+ handler_task_group: anyio.abc.TaskGroup,
58
+ handler: ta.Callable[[anyio.abc.SocketStream], ta.Awaitable],
59
+ ) -> ta.NoReturn:
60
+ async with listener:
61
+ while True:
62
+ try:
63
+ stream = await listener.accept()
64
+ except OSError as exc:
65
+ if exc.errno in ACCEPT_CAPACITY_ERRNOS:
66
+ log.exception(
67
+ 'accept returned %s (%s); retrying in %s seconds',
68
+ errno.errorcode[exc.errno],
69
+ os.strerror(exc.errno),
70
+ SLEEP_TIME,
71
+ )
72
+ await anyio.sleep(SLEEP_TIME)
73
+ else:
74
+ raise
75
+ else:
76
+ handler_task_group.start_soon(_run_handler, stream, handler)
77
+
78
+
79
+ async def serve_listeners(
80
+ handler: ta.Callable[[anyio.abc.SocketStream], ta.Awaitable],
81
+ listeners: ta.Iterable[anyio.abc.SocketListener],
82
+ *,
83
+ handler_task_group: anyio.abc.TaskGroup | None = None,
84
+ task_status: anyio.abc.TaskStatus[ta.Iterable[anyio.abc.SocketListener]] = anyio.TASK_STATUS_IGNORED,
85
+ ) -> ta.NoReturn:
86
+ async with anyio.create_task_group() as task_group:
87
+ if handler_task_group is None:
88
+ handler_task_group = task_group
89
+ for listener in listeners:
90
+ task_group.start_soon(_serve_one_listener, listener, handler_task_group, handler)
91
+ # The listeners are already queueing connections when we're called, but we wait until the end to call started()
92
+ # just in case we get an error or whatever.
93
+ task_status.started(listeners)
94
+
95
+ raise RuntimeError('unreachable')
96
+
97
+
98
+ async def _install_signal_handler(
99
+ tg: anyio.abc.TaskGroup,
100
+ ) -> ta.Callable[..., ta.Awaitable[None]] | None:
101
+ signal_event = anyio.Event()
102
+
103
+ sigs = [
104
+ getattr(signal, signal_name)
105
+ for signal_name in ('SIGINT', 'SIGTERM', 'SIGBREAK')
106
+ if hasattr(signal, signal_name)
107
+ ]
108
+
109
+ if not sigs:
110
+ return None
111
+
112
+ async def _handler(*, task_status=anyio.TASK_STATUS_IGNORED):
113
+ with anyio.open_signal_receiver(signal.SIGINT, signal.SIGTERM) as signals:
114
+ task_status.started()
115
+ async for signum in signals:
116
+ if signum == signal.SIGINT:
117
+ print('Ctrl+C pressed!')
118
+ else:
119
+ print('Terminated!')
120
+
121
+ signal_event.set()
122
+ return
123
+
124
+ await tg.start(_handler)
125
+ return signal_event.wait
126
+
127
+
128
+ async def serve(
129
+ app: AsgiFramework | AppWrapper,
130
+ config: Config,
131
+ *,
132
+ sockets: Sockets | None = None,
133
+ shutdown_trigger: ta.Callable[..., ta.Awaitable[None]] | None = None,
134
+ handle_shutdown_signals: bool = False,
135
+ task_status: anyio.abc.TaskStatus[ta.Sequence[str]] = anyio.TASK_STATUS_IGNORED,
136
+ ) -> None:
137
+ app = wrap_app(app)
138
+
139
+ lifespan = Lifespan(app, config)
140
+ max_requests = None
141
+ if config.max_requests is not None:
142
+ max_requests = config.max_requests + random.randint(0, config.max_requests_jitter)
143
+ context = WorkerContext(max_requests)
144
+
145
+ async with anyio.create_task_group() as lifespan_task_group:
146
+ if shutdown_trigger is None and handle_shutdown_signals:
147
+ shutdown_trigger = await _install_signal_handler(lifespan_task_group)
148
+
149
+ await lifespan_task_group.start(lifespan.handle_lifespan)
150
+ await lifespan.wait_for_startup()
151
+
152
+ async with anyio.create_task_group() as server_task_group:
153
+ if sockets is None:
154
+ sockets = create_sockets(config)
155
+ for sock in sockets.insecure_sockets:
156
+ sock.listen(config.backlog)
157
+
158
+ listeners = []
159
+ binds = []
160
+
161
+ for sock in sockets.insecure_sockets:
162
+ listeners.append(anyio._core._eventloop.get_async_backend().create_tcp_listener(sock)) # noqa
163
+ bind = repr_socket_addr(sock.family, sock.getsockname())
164
+ binds.append(f'http://{bind}')
165
+ log.info('Running on http://%s (CTRL + C to quit)', bind)
166
+
167
+ task_status.started(binds)
168
+ try:
169
+ async with anyio.create_task_group() as task_group:
170
+ if shutdown_trigger is not None:
171
+ task_group.start_soon(raise_shutdown, shutdown_trigger)
172
+ task_group.start_soon(raise_shutdown, context.terminate.wait)
173
+
174
+ task_group.start_soon(
175
+ functools.partial(
176
+ serve_listeners,
177
+ functools.partial(TcpServer, app, config, context),
178
+ listeners,
179
+ handler_task_group=server_task_group,
180
+ ),
181
+ )
182
+
183
+ await anyio.sleep_forever()
184
+ except BaseExceptionGroup as error:
185
+ _, other_errors = error.split((ShutdownError, KeyboardInterrupt)) # noqa
186
+ if other_errors is not None:
187
+ raise other_errors # noqa
188
+ finally:
189
+ await context.terminated.set()
190
+ server_task_group.cancel_scope.deadline = anyio.current_time() + config.graceful_timeout
191
+
192
+ await lifespan.wait_for_shutdown()
193
+ lifespan_task_group.cancel_scope.cancel()
@@ -0,0 +1,21 @@
1
+ Copyright 2023- wrmsr
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
+ following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
+ disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10
+ disclaimer in the documentation and/or other materials provided with the distribution.
11
+
12
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.1
2
+ Name: omserv
3
+ Version: 0.0.0.dev7
4
+ Summary: omserv
5
+ Author: wrmsr
6
+ License: BSD-3-Clause
7
+ Project-URL: source, https://github.com/wrmsr/omlish
8
+ Classifier: License :: OSI Approved :: BSD License
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Operating System :: POSIX
13
+ Requires-Python: >=3.12
14
+ License-File: LICENSE
15
+ Requires-Dist: omlish ==0.0.0.dev7
16
+ Provides-Extra: server
17
+ Requires-Dist: h11 >=0.14 ; extra == 'server'
18
+ Requires-Dist: h2 >=4.1 ; extra == 'server'
19
+ Requires-Dist: priority >=2 ; extra == 'server'
20
+ Requires-Dist: wsproto >=1.2 ; extra == 'server'
21
+
@@ -0,0 +1,44 @@
1
+ omserv/__about__.py,sha256=SVIHOVKqN_bCysy9qjnPhoEnzysjl_l7dXo85n3rMS0,616
2
+ omserv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ omserv/dbs.py,sha256=RLXwNmENmM9MLsjFqDho_R8ibMjOKAxWUn4zhzTlP9Y,756
4
+ omserv/secrets.py,sha256=ja0VsCB01MHxYwn5OHjFeXV9cRah9AQl-0uJzZELpic,256
5
+ omserv/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ omserv/apps/base.py,sha256=KSwxbC0_fY87_DucvpEOHG6gZ2wL78D-N3We8RTEU4Q,385
7
+ omserv/apps/inject.py,sha256=RZjPcB_xEPhdw5Ki3rnKeHMqhJ8zFJZ_QZSrKpH-0xM,2670
8
+ omserv/apps/markers.py,sha256=LBVx9LTyKB2sPqDrMHjwVpqH3xDIRqMjlFFw1HsXOWI,867
9
+ omserv/apps/routes.py,sha256=shcN8qCSF2YoKal7nk-lemCAK3RX8MuHgNHhq_CTnh0,3762
10
+ omserv/apps/sessions.py,sha256=glruQSbOSbCYLPp6nDRNSHCyp5hj4oiOPhh3R0F9BTM,1537
11
+ omserv/apps/templates.py,sha256=NyCsKmPaLsGYc1mWyGiC7EcYsisQhq-wWdrnHnbwxC8,2095
12
+ omserv/node/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ omserv/node/models.py,sha256=XsfAI-LjlRkRfK0_ofh-gai5IUq_g357UfThK9dZ0UM,1258
14
+ omserv/node/registry.py,sha256=0hodmyQn3POOZJBG8zTr9ojR80-ZNmIwJpQbchJg8EM,3459
15
+ omserv/node/sql.py,sha256=vy7RP50JiH3jQHMVa7Hxk0pFJK3QcbGeTvyNppB1W4I,2826
16
+ omserv/server/__init__.py,sha256=K8l4nfv9c_5o8dph-SyadW_19CdTC2FGlikyHMU5lAU,375
17
+ omserv/server/config.py,sha256=oGWL1kuk45bJ6sVr8n3ow5Q-1nz9EqByjoykU2iOHIY,1189
18
+ omserv/server/debug.py,sha256=N7RI0Jj-ttmys3DJD0RREmGG5XZpTCp6y9Yu0x98Agg,299
19
+ omserv/server/events.py,sha256=VMr_rArsVjJYnyH9SqLWtOLUg18vSu1O0ep9gNBGR_c,1369
20
+ omserv/server/headers.py,sha256=3H-NxMMQg5WuF5wF4AWFUEqkToh4NqNqHouavzbOQok,1188
21
+ omserv/server/lifespans.py,sha256=kRVxDQM18jCBzRUpafyb69q_bGSCyxxjAtrkxjqcZdE,4607
22
+ omserv/server/multiprocess.py,sha256=xEQ4Na-Nej3oO_cxvYmQ1vEKtuLJNirrfRRMeKTxtVo,4377
23
+ omserv/server/sockets.py,sha256=lwqNP7URlp605ibsjHzp0pc-lyjcyTu-hD-uyojLUYk,3389
24
+ omserv/server/ssl.py,sha256=gmB5ecM8Mck-YtGYF8pb2dwFdjABVGzERFCDzM9lBck,1483
25
+ omserv/server/taskspawner.py,sha256=ljzF26UPtnp7GLAY_BvjzuwCoCO9aL7TKLwRNTmUy1M,3008
26
+ omserv/server/tcpserver.py,sha256=akC-2WOhmoIiJBH0Ti0m1uK_sOTBYGie0CoRkEcUmkA,5082
27
+ omserv/server/types.py,sha256=XXY5py8RYlEeD4FZrWNqSyX7DD-ffSlcG-T2s9BY9eI,2017
28
+ omserv/server/workercontext.py,sha256=4rcLuGsyiU7URO7T_eHylOBPPNUS9C23QfEUVyJUtIY,1200
29
+ omserv/server/workers.py,sha256=rdV8qEzWKMZ6HnN9nUoGS58U9LRsrsqOcAd_7yl73Y0,6586
30
+ omserv/server/protocols/__init__.py,sha256=Ryu2PDZ1TUI6F2l-HBEYgyzZ7wHqE6VmjqnS0tIvmQI,47
31
+ omserv/server/protocols/h11.py,sha256=ctakfp8fyI2opzIWzwV1FGlnJlWL3QkMABp-qoPYFXI,12050
32
+ omserv/server/protocols/h2.py,sha256=pClkPlaX0WnQ1dKee7Fvr4csSH2bBqItTcsKVuk1p1Q,15297
33
+ omserv/server/protocols/protocols.py,sha256=1ky8PGYIIR8wco6fcw-41RmHW-Kz_cNcZZoOqi7btNk,2790
34
+ omserv/server/protocols/types.py,sha256=OXt5U3LsBToDxT7ipfFGiIHJU0WVvlfT6QQacF-_Plc,440
35
+ omserv/server/resources/__init__.py,sha256=KppZbvZBKX1TdyaQZYY92oEKpdB3nuUXxharAgrbAIA,193
36
+ omserv/server/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ omserv/server/streams/httpstream.py,sha256=0DeiAPLGbEGNa0fHTs8lUpi_CFZs4M5_QB-TiS8mobQ,8015
38
+ omserv/server/streams/utils.py,sha256=aMOrqWIg_Hht5W4kLg3y7oR5AEkVvMrZhyjzo6U5owE,1527
39
+ omserv/server/streams/wsstream.py,sha256=3Vyzox7dCE1tDSXjb6xBubWo41ZF9d38Hrsrlj6h1J8,15482
40
+ omserv-0.0.0.dev7.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
41
+ omserv-0.0.0.dev7.dist-info/METADATA,sha256=Y7anWoLnYvQ2eCXsmCjKdXGMIApFW1sKmz0ebI1OG64,680
42
+ omserv-0.0.0.dev7.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
43
+ omserv-0.0.0.dev7.dist-info/top_level.txt,sha256=HXehpnxeKscKNULzKNzZ27oNawBrsh1PaNAirbX-XNA,7
44
+ omserv-0.0.0.dev7.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (73.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ omserv