runtimepy 5.12.2__py3-none-any.whl → 5.13.0__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.
- runtimepy/__init__.py +2 -2
- runtimepy/commands/server.py +0 -1
- runtimepy/net/http/response.py +32 -0
- runtimepy/net/server/__init__.py +7 -7
- runtimepy/net/tcp/http/__init__.py +24 -14
- {runtimepy-5.12.2.dist-info → runtimepy-5.13.0.dist-info}/METADATA +5 -5
- {runtimepy-5.12.2.dist-info → runtimepy-5.13.0.dist-info}/RECORD +11 -11
- {runtimepy-5.12.2.dist-info → runtimepy-5.13.0.dist-info}/WHEEL +1 -1
- {runtimepy-5.12.2.dist-info → runtimepy-5.13.0.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.12.2.dist-info → runtimepy-5.13.0.dist-info}/licenses/LICENSE +0 -0
- {runtimepy-5.12.2.dist-info → runtimepy-5.13.0.dist-info}/top_level.txt +0 -0
runtimepy/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# =====================================
|
|
2
2
|
# generator=datazen
|
|
3
3
|
# version=3.2.1
|
|
4
|
-
# hash=
|
|
4
|
+
# hash=35e5555173fdfd064e5047172de9b6a4
|
|
5
5
|
# =====================================
|
|
6
6
|
|
|
7
7
|
"""
|
|
@@ -10,7 +10,7 @@ Useful defaults and other package metadata.
|
|
|
10
10
|
|
|
11
11
|
DESCRIPTION = "A framework for implementing Python services."
|
|
12
12
|
PKG_NAME = "runtimepy"
|
|
13
|
-
VERSION = "5.
|
|
13
|
+
VERSION = "5.13.0"
|
|
14
14
|
|
|
15
15
|
# runtimepy-specific content.
|
|
16
16
|
METRICS_NAME = "metrics"
|
runtimepy/commands/server.py
CHANGED
runtimepy/net/http/response.py
CHANGED
|
@@ -6,8 +6,12 @@ A module implementing HTTP-response interfaces.
|
|
|
6
6
|
import http
|
|
7
7
|
from io import StringIO
|
|
8
8
|
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import AsyncIterator, cast
|
|
9
11
|
|
|
10
12
|
# third-party
|
|
13
|
+
import aiofiles
|
|
14
|
+
import aiofiles.os
|
|
11
15
|
from vcorelib.logging import LoggerType
|
|
12
16
|
|
|
13
17
|
# internal
|
|
@@ -83,3 +87,31 @@ class ResponseHeader(HeadersMixin):
|
|
|
83
87
|
self.status_line,
|
|
84
88
|
self.headers,
|
|
85
89
|
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AsyncResponse:
|
|
93
|
+
"""
|
|
94
|
+
A class facilitating asynchronous server responses for e.g.
|
|
95
|
+
file-system files.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, path: Path, chunk_size: int = 1024) -> None:
|
|
99
|
+
"""Initialize this instance."""
|
|
100
|
+
|
|
101
|
+
self.path = path
|
|
102
|
+
self.chunk_size = chunk_size
|
|
103
|
+
|
|
104
|
+
async def size(self) -> int:
|
|
105
|
+
"""Get this response's size."""
|
|
106
|
+
return cast(int, await aiofiles.os.path.getsize(self.path))
|
|
107
|
+
|
|
108
|
+
async def process(self) -> AsyncIterator[bytes]:
|
|
109
|
+
"""Yield chunks to write asynchronously."""
|
|
110
|
+
|
|
111
|
+
async with aiofiles.open(self.path, mode="rb") as path_fd:
|
|
112
|
+
while True:
|
|
113
|
+
data = await path_fd.read(self.chunk_size)
|
|
114
|
+
if data:
|
|
115
|
+
yield data
|
|
116
|
+
else:
|
|
117
|
+
break
|
runtimepy/net/server/__init__.py
CHANGED
|
@@ -21,11 +21,11 @@ from runtimepy.channel.environment.command import GLOBAL
|
|
|
21
21
|
from runtimepy.net.html import full_markdown_page
|
|
22
22
|
from runtimepy.net.http.header import RequestHeader
|
|
23
23
|
from runtimepy.net.http.request_target import PathMaybeQuery
|
|
24
|
-
from runtimepy.net.http.response import ResponseHeader
|
|
24
|
+
from runtimepy.net.http.response import AsyncResponse, ResponseHeader
|
|
25
25
|
from runtimepy.net.server.html import HtmlApp, HtmlApps, get_html, html_handler
|
|
26
26
|
from runtimepy.net.server.json import encode_json, json_handler
|
|
27
27
|
from runtimepy.net.server.markdown import markdown_for_dir
|
|
28
|
-
from runtimepy.net.tcp.http import HttpConnection
|
|
28
|
+
from runtimepy.net.tcp.http import HttpConnection, HttpResult
|
|
29
29
|
from runtimepy.util import normalize_root, path_has_part, read_binary
|
|
30
30
|
|
|
31
31
|
MIMETYPES_INIT = False
|
|
@@ -173,10 +173,10 @@ class RuntimepyServerConnection(HttpConnection):
|
|
|
173
173
|
|
|
174
174
|
async def try_file(
|
|
175
175
|
self, path: PathMaybeQuery, response: ResponseHeader
|
|
176
|
-
) ->
|
|
176
|
+
) -> HttpResult:
|
|
177
177
|
"""Try serving this path as a file directly from the file-system."""
|
|
178
178
|
|
|
179
|
-
result = None
|
|
179
|
+
result: HttpResult = None
|
|
180
180
|
|
|
181
181
|
# Keep track of directories encountered.
|
|
182
182
|
directories: list[Path] = []
|
|
@@ -214,7 +214,7 @@ class RuntimepyServerConnection(HttpConnection):
|
|
|
214
214
|
self.logger.info("Serving '%s' (MIME: %s)", candidate, mime)
|
|
215
215
|
|
|
216
216
|
# Return the file data.
|
|
217
|
-
result =
|
|
217
|
+
result = AsyncResponse(candidate)
|
|
218
218
|
break
|
|
219
219
|
|
|
220
220
|
# Handle a directory as a last resort.
|
|
@@ -259,7 +259,7 @@ class RuntimepyServerConnection(HttpConnection):
|
|
|
259
259
|
response: ResponseHeader,
|
|
260
260
|
request: RequestHeader,
|
|
261
261
|
request_data: Optional[bytes],
|
|
262
|
-
) ->
|
|
262
|
+
) -> HttpResult:
|
|
263
263
|
"""Handle POST requests."""
|
|
264
264
|
|
|
265
265
|
request.log(self.logger, False, level=logging.INFO)
|
|
@@ -281,7 +281,7 @@ class RuntimepyServerConnection(HttpConnection):
|
|
|
281
281
|
response: ResponseHeader,
|
|
282
282
|
request: RequestHeader,
|
|
283
283
|
request_data: Optional[bytes],
|
|
284
|
-
) ->
|
|
284
|
+
) -> HttpResult:
|
|
285
285
|
"""Handle GET requests."""
|
|
286
286
|
|
|
287
287
|
request.log(self.logger, False, level=logging.INFO)
|
|
@@ -16,22 +16,24 @@ from vcorelib import DEFAULT_ENCODING
|
|
|
16
16
|
from runtimepy import PKG_NAME, VERSION
|
|
17
17
|
from runtimepy.net.http import HttpMessageProcessor
|
|
18
18
|
from runtimepy.net.http.header import RequestHeader
|
|
19
|
-
from runtimepy.net.http.response import ResponseHeader
|
|
19
|
+
from runtimepy.net.http.response import AsyncResponse, ResponseHeader
|
|
20
20
|
from runtimepy.net.tcp.connection import TcpConnection as _TcpConnection
|
|
21
21
|
|
|
22
|
+
HttpResult = Optional[bytes | AsyncResponse]
|
|
23
|
+
|
|
22
24
|
#
|
|
23
25
|
# async def handler(
|
|
24
26
|
# response: ResponseHeader,
|
|
25
27
|
# request: RequestHeader,
|
|
26
28
|
# request_data: Optional[bytes],
|
|
27
|
-
# ) ->
|
|
29
|
+
# ) -> HttpResult:
|
|
28
30
|
# """Sample handler."""
|
|
29
31
|
#
|
|
30
32
|
HttpRequestHandler = Callable[
|
|
31
33
|
[ResponseHeader, RequestHeader, Optional[bytes]],
|
|
32
|
-
Awaitable[
|
|
34
|
+
Awaitable[HttpResult],
|
|
33
35
|
]
|
|
34
|
-
HttpResponse = Tuple[ResponseHeader,
|
|
36
|
+
HttpResponse = Tuple[ResponseHeader, HttpResult]
|
|
35
37
|
|
|
36
38
|
HttpRequestHandlers = dict[http.HTTPMethod, HttpRequestHandler]
|
|
37
39
|
|
|
@@ -89,7 +91,7 @@ class HttpConnection(_TcpConnection):
|
|
|
89
91
|
response: ResponseHeader,
|
|
90
92
|
request: RequestHeader,
|
|
91
93
|
request_data: Optional[bytes],
|
|
92
|
-
) ->
|
|
94
|
+
) -> HttpResult:
|
|
93
95
|
"""Sample handler."""
|
|
94
96
|
|
|
95
97
|
async def post_handler(
|
|
@@ -97,7 +99,7 @@ class HttpConnection(_TcpConnection):
|
|
|
97
99
|
response: ResponseHeader,
|
|
98
100
|
request: RequestHeader,
|
|
99
101
|
request_data: Optional[bytes],
|
|
100
|
-
) ->
|
|
102
|
+
) -> HttpResult:
|
|
101
103
|
"""Sample handler."""
|
|
102
104
|
|
|
103
105
|
async def _process_request(
|
|
@@ -105,7 +107,7 @@ class HttpConnection(_TcpConnection):
|
|
|
105
107
|
response: ResponseHeader,
|
|
106
108
|
request_header: RequestHeader,
|
|
107
109
|
request_data: Optional[bytes] = None,
|
|
108
|
-
) ->
|
|
110
|
+
) -> HttpResult:
|
|
109
111
|
"""Process an individual request."""
|
|
110
112
|
|
|
111
113
|
result = None
|
|
@@ -137,7 +139,7 @@ class HttpConnection(_TcpConnection):
|
|
|
137
139
|
# Set boilerplate header data.
|
|
138
140
|
request["user-agent"] = self.identity
|
|
139
141
|
|
|
140
|
-
self._send(request, data)
|
|
142
|
+
await self._send(request, data)
|
|
141
143
|
self.expecting_response = True
|
|
142
144
|
result = await self.responses.get()
|
|
143
145
|
self.expecting_response = False
|
|
@@ -153,21 +155,29 @@ class HttpConnection(_TcpConnection):
|
|
|
153
155
|
"""
|
|
154
156
|
return to_json(await self.request(request, data))
|
|
155
157
|
|
|
156
|
-
def _send(
|
|
158
|
+
async def _send(
|
|
157
159
|
self,
|
|
158
160
|
header: Union[ResponseHeader, RequestHeader],
|
|
159
|
-
data:
|
|
161
|
+
data: HttpResult = None,
|
|
160
162
|
) -> None:
|
|
161
163
|
"""Send a request or response to a request."""
|
|
162
164
|
|
|
163
165
|
# Set content length.
|
|
164
166
|
header["content-length"] = "0"
|
|
165
|
-
|
|
167
|
+
|
|
168
|
+
if isinstance(data, AsyncResponse):
|
|
169
|
+
header["content-length"] = str(await data.size())
|
|
170
|
+
elif data is not None:
|
|
166
171
|
header["content-length"] = str(len(data))
|
|
167
172
|
|
|
168
173
|
self.send_binary(bytes(header))
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
|
|
175
|
+
if data is not None:
|
|
176
|
+
if isinstance(data, AsyncResponse):
|
|
177
|
+
async for chunk in data.process():
|
|
178
|
+
self.send_binary(chunk)
|
|
179
|
+
else:
|
|
180
|
+
self.send_binary(data)
|
|
171
181
|
|
|
172
182
|
header.log(self.logger, True)
|
|
173
183
|
|
|
@@ -183,7 +193,7 @@ class HttpConnection(_TcpConnection):
|
|
|
183
193
|
if not self.expecting_response:
|
|
184
194
|
# Process request.
|
|
185
195
|
response = ResponseHeader()
|
|
186
|
-
self._send(
|
|
196
|
+
await self._send(
|
|
187
197
|
response,
|
|
188
198
|
await self._process_request(
|
|
189
199
|
response, cast(RequestHeader, header), payload
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtimepy
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.13.0
|
|
4
4
|
Summary: A framework for implementing Python services.
|
|
5
5
|
Home-page: https://github.com/libre-embedded/runtimepy
|
|
6
6
|
Author: Libre Embedded
|
|
@@ -17,11 +17,11 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
17
17
|
Requires-Python: >=3.12
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
|
+
Requires-Dist: svgen>=0.7.4
|
|
20
21
|
Requires-Dist: aiofiles
|
|
21
22
|
Requires-Dist: psutil
|
|
22
|
-
Requires-Dist: vcorelib>=3.5.1
|
|
23
23
|
Requires-Dist: websockets
|
|
24
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: vcorelib>=3.5.1
|
|
25
25
|
Provides-Extra: test
|
|
26
26
|
Requires-Dist: pylint; extra == "test"
|
|
27
27
|
Requires-Dist: flake8; extra == "test"
|
|
@@ -51,11 +51,11 @@ Dynamic: requires-python
|
|
|
51
51
|
=====================================
|
|
52
52
|
generator=datazen
|
|
53
53
|
version=3.2.1
|
|
54
|
-
hash=
|
|
54
|
+
hash=4f0e82dc45d3ea9c4ccf5b262413c206
|
|
55
55
|
=====================================
|
|
56
56
|
-->
|
|
57
57
|
|
|
58
|
-
# runtimepy ([5.
|
|
58
|
+
# runtimepy ([5.13.0](https://pypi.org/project/runtimepy/))
|
|
59
59
|
|
|
60
60
|
[](https://pypi.org/project/runtimepy/)
|
|
61
61
|

|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
runtimepy/__init__.py,sha256=
|
|
1
|
+
runtimepy/__init__.py,sha256=HxjInsMBuhZ8HVgWbkL2C4cBfavqCLORI0hiaUc4Lq0,391
|
|
2
2
|
runtimepy/__main__.py,sha256=3IFTvHw-vqPIFy6ZZjzl00oDwFLzcDmV5WHo3ohPKbc,332
|
|
3
3
|
runtimepy/app.py,sha256=xhqZOvBcBCawUR2Qd5oyvrmNdOJhdChgddb0GiJOnPc,970
|
|
4
4
|
runtimepy/dev_requirements.txt,sha256=VZhW6bJ5YbwaoN4d_XxZFuN5BbDLaG7ngKrGnugVPRw,245
|
|
@@ -33,7 +33,7 @@ runtimepy/commands/all.py,sha256=nANQ5v5PlIhY8TZejcckGoZmqjOr7tYA7LstordQUDk,161
|
|
|
33
33
|
runtimepy/commands/arbiter.py,sha256=CtTMRYpqCAN3vWHkkr9jqWpoF7JGNXafKIBFmkarAfc,1567
|
|
34
34
|
runtimepy/commands/common.py,sha256=NvZdeIFBHAF52c1n7vqD59DW6ywc-rG5iC5MpuhGf-c,2449
|
|
35
35
|
runtimepy/commands/mtu.py,sha256=LFFjTU4SsuV3j7Mhx_WuKa5lfdfMm70zJvDWToVrP7E,1357
|
|
36
|
-
runtimepy/commands/server.py,sha256=
|
|
36
|
+
runtimepy/commands/server.py,sha256=q49WlQQWYBcHrKQo7hCc3Y6hNbG2DNLMXIlLg8Mc8lA,4655
|
|
37
37
|
runtimepy/commands/task.py,sha256=6xRVlRwpEZVhrcY18sQcfdWEOxeQZLeOF-6UrUURtO4,1435
|
|
38
38
|
runtimepy/commands/tftp.py,sha256=djFQzYDxy2jvUseHJ4fDR3CowPxQ-Tu0IJz1SwapXX0,2361
|
|
39
39
|
runtimepy/commands/tui.py,sha256=9hWA3_YATibUUDTVQr7UnKzPTDVJ7WxWKTYYQpLoyrE,1869
|
|
@@ -190,10 +190,10 @@ runtimepy/net/http/__init__.py,sha256=4TjFp_ajAVcOEvwtjlF6mG-9EbEePqFZht-QpWIKVB
|
|
|
190
190
|
runtimepy/net/http/common.py,sha256=vpoO6XwRmrZmTkCu9bkI0HnyaD8MWTpV7ADesCNrfRE,2237
|
|
191
191
|
runtimepy/net/http/header.py,sha256=AECSdvhBA9_5Pg3UdwMzsmBpcqgsiPj41xnIGPm5g5E,2296
|
|
192
192
|
runtimepy/net/http/request_target.py,sha256=EE1aI5VSARw1h93jyZvP56ir5O5fjd6orYK-1FM4JNk,1550
|
|
193
|
-
runtimepy/net/http/response.py,sha256=
|
|
193
|
+
runtimepy/net/http/response.py,sha256=y33KLUJNE7zx6biUMwTkUfQ1bXiKV6pjrcxuh8U3WCE,3216
|
|
194
194
|
runtimepy/net/http/state.py,sha256=qCMN8aWfCRfU9XP-cIhSOo2RqfljTjbQRCflfcy2bfY,1626
|
|
195
195
|
runtimepy/net/http/version.py,sha256=mp6rgIM7-VUVKLCA0Uw96CmBkL0ET860lDVVEewpZ7w,1098
|
|
196
|
-
runtimepy/net/server/__init__.py,sha256=
|
|
196
|
+
runtimepy/net/server/__init__.py,sha256=R6xV5wUqrCwB0pa0FMjcZ-txkT87H_B9AgWq0RrJQsU,10458
|
|
197
197
|
runtimepy/net/server/html.py,sha256=ufg0PQF_iUE7PT0n3Pn3jTcun7mspZUI6_ooblcNnvI,1217
|
|
198
198
|
runtimepy/net/server/json.py,sha256=a7vM5yfq2er4DexzFqEMnxoMGDeuywKkVH4-uNJBAik,2522
|
|
199
199
|
runtimepy/net/server/markdown.py,sha256=DFjGbvIST4HXRhtTTvXVQ9ZAwrIfVzPlcU1dW8JYya8,1381
|
|
@@ -227,7 +227,7 @@ runtimepy/net/tcp/__init__.py,sha256=OOWohegpoioSTf8M7uDf-4EV1IDungz7-U19L_2yW4I
|
|
|
227
227
|
runtimepy/net/tcp/connection.py,sha256=sYWj2aDiAHQf70zfRJM24cHraFd_TuzTD4fRWeSQZXE,8811
|
|
228
228
|
runtimepy/net/tcp/create.py,sha256=zZsRs5KYpO3bNGh-DwEOEzjUDE4ixj-UBHYgZ0GvC7c,2013
|
|
229
229
|
runtimepy/net/tcp/protocol.py,sha256=vEnIX3gUX2nrw9ofT_e4KYU4VY2k4WP0WuOi4eE_OOQ,1444
|
|
230
|
-
runtimepy/net/tcp/http/__init__.py,sha256=
|
|
230
|
+
runtimepy/net/tcp/http/__init__.py,sha256=SPvrZvIM1poE_jgCNY6gjgV2bXfMmPH_0zdx8x4JSVI,6080
|
|
231
231
|
runtimepy/net/tcp/scpi/__init__.py,sha256=aWCWQfdeyfoU9bpOnOtyIQbT1swl4ergXLFn5kXAH28,2105
|
|
232
232
|
runtimepy/net/tcp/telnet/__init__.py,sha256=96eJFb301I3H2ivDtGMQtDDw09Xm5NRvM9VEC-wjt8c,4768
|
|
233
233
|
runtimepy/net/tcp/telnet/codes.py,sha256=1-yyRe-Kz_W7d6B0P3iT1AaSNR3_Twmn-MUjKCJJknY,3518
|
|
@@ -300,9 +300,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
|
|
|
300
300
|
runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
|
|
301
301
|
runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
302
302
|
runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
|
|
303
|
-
runtimepy-5.
|
|
304
|
-
runtimepy-5.
|
|
305
|
-
runtimepy-5.
|
|
306
|
-
runtimepy-5.
|
|
307
|
-
runtimepy-5.
|
|
308
|
-
runtimepy-5.
|
|
303
|
+
runtimepy-5.13.0.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
|
|
304
|
+
runtimepy-5.13.0.dist-info/METADATA,sha256=TEucfXpt7dgg0L_P1F-WItb0l7MJuXLyuejhWBc1Uv0,9268
|
|
305
|
+
runtimepy-5.13.0.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
|
|
306
|
+
runtimepy-5.13.0.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
|
|
307
|
+
runtimepy-5.13.0.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
|
|
308
|
+
runtimepy-5.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|