runtimepy 5.15.6__py3-none-any.whl → 5.15.8__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/data/css/main.css +4 -0
- runtimepy/net/http/response.py +18 -4
- runtimepy/net/server/__init__.py +15 -3
- runtimepy/net/server/markdown.py +37 -8
- runtimepy/net/tcp/http/__init__.py +5 -5
- {runtimepy-5.15.6.dist-info → runtimepy-5.15.8.dist-info}/METADATA +6 -6
- {runtimepy-5.15.6.dist-info → runtimepy-5.15.8.dist-info}/RECORD +12 -12
- {runtimepy-5.15.6.dist-info → runtimepy-5.15.8.dist-info}/WHEEL +0 -0
- {runtimepy-5.15.6.dist-info → runtimepy-5.15.8.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.15.6.dist-info → runtimepy-5.15.8.dist-info}/licenses/LICENSE +0 -0
- {runtimepy-5.15.6.dist-info → runtimepy-5.15.8.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.3
|
|
4
|
-
# hash=
|
|
4
|
+
# hash=be3f79364e840f1950bc121205810f02
|
|
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.15.
|
|
13
|
+
VERSION = "5.15.8"
|
|
14
14
|
|
|
15
15
|
# runtimepy-specific content.
|
|
16
16
|
METRICS_NAME = "metrics"
|
runtimepy/data/css/main.css
CHANGED
runtimepy/net/http/response.py
CHANGED
|
@@ -3,11 +3,12 @@ A module implementing HTTP-response interfaces.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# built-in
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
6
7
|
import http
|
|
7
8
|
from io import StringIO
|
|
8
9
|
import logging
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import AsyncIterator, cast
|
|
11
|
+
from typing import AsyncIterator, Optional, cast
|
|
11
12
|
|
|
12
13
|
# third-party
|
|
13
14
|
import aiofiles
|
|
@@ -96,19 +97,32 @@ class ResponseHeader(HeadersMixin):
|
|
|
96
97
|
self["Cache-Control"] = value
|
|
97
98
|
|
|
98
99
|
|
|
99
|
-
class AsyncResponse:
|
|
100
|
+
class AsyncResponse(ABC):
|
|
101
|
+
"""Interface for asynchronous responses."""
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
async def size(self) -> Optional[int]:
|
|
105
|
+
"""Get this response's size."""
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
async def process(self) -> AsyncIterator[bytes]:
|
|
109
|
+
"""Yield chunks to write asynchronously."""
|
|
110
|
+
yield bytes() # pragma: nocover
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class AsyncFile(AsyncResponse):
|
|
100
114
|
"""
|
|
101
115
|
A class facilitating asynchronous server responses for e.g.
|
|
102
116
|
file-system files.
|
|
103
117
|
"""
|
|
104
118
|
|
|
105
|
-
def __init__(self, path: Path, chunk_size: int =
|
|
119
|
+
def __init__(self, path: Path, chunk_size: int = 4096) -> None:
|
|
106
120
|
"""Initialize this instance."""
|
|
107
121
|
|
|
108
122
|
self.path = path
|
|
109
123
|
self.chunk_size = chunk_size
|
|
110
124
|
|
|
111
|
-
async def size(self) -> int:
|
|
125
|
+
async def size(self) -> Optional[int]:
|
|
112
126
|
"""Get this response's size."""
|
|
113
127
|
return cast(int, await aiofiles.os.path.getsize(self.path))
|
|
114
128
|
|
runtimepy/net/server/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ from io import StringIO
|
|
|
8
8
|
import logging
|
|
9
9
|
import mimetypes
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Optional, TextIO, Union
|
|
11
|
+
from typing import Any, Awaitable, Callable, Optional, TextIO, Union
|
|
12
12
|
from urllib.parse import urlencode
|
|
13
13
|
|
|
14
14
|
# third-party
|
|
@@ -22,7 +22,7 @@ from runtimepy.channel.environment.command import GLOBAL, global_command
|
|
|
22
22
|
from runtimepy.net.html import full_markdown_page
|
|
23
23
|
from runtimepy.net.http.header import RequestHeader
|
|
24
24
|
from runtimepy.net.http.request_target import PathMaybeQuery
|
|
25
|
-
from runtimepy.net.http.response import
|
|
25
|
+
from runtimepy.net.http.response import AsyncFile, ResponseHeader
|
|
26
26
|
from runtimepy.net.server.html import HtmlApp, HtmlApps, get_html, html_handler
|
|
27
27
|
from runtimepy.net.server.json import encode_json, json_handler
|
|
28
28
|
from runtimepy.net.server.markdown import DIR_FILE, markdown_for_dir
|
|
@@ -41,11 +41,17 @@ def package_data_dir() -> Path:
|
|
|
41
41
|
return result.parent
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
CustomAsync = Callable[
|
|
45
|
+
[ResponseHeader, RequestHeader, Optional[bytearray]], Awaitable[HttpResult]
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
44
49
|
class RuntimepyServerConnection(HttpConnection):
|
|
45
50
|
"""A class implementing a server-connection interface for this package."""
|
|
46
51
|
|
|
47
52
|
# Can register application methods to URL paths.
|
|
48
53
|
apps: HtmlApps = {"/mux.html": mux_app}
|
|
54
|
+
custom: dict[str, CustomAsync] = {}
|
|
49
55
|
default_app: Optional[HtmlApp] = None
|
|
50
56
|
|
|
51
57
|
# Can load additional data into this dictionary for easy HTTP access.
|
|
@@ -245,7 +251,7 @@ class RuntimepyServerConnection(HttpConnection):
|
|
|
245
251
|
response.static_resource()
|
|
246
252
|
|
|
247
253
|
# Return the file data.
|
|
248
|
-
result =
|
|
254
|
+
result = AsyncFile(candidate)
|
|
249
255
|
break
|
|
250
256
|
|
|
251
257
|
# Handle a directory as a last resort.
|
|
@@ -335,6 +341,12 @@ class RuntimepyServerConnection(HttpConnection):
|
|
|
335
341
|
response.static_resource()
|
|
336
342
|
return self.favicon_data
|
|
337
343
|
|
|
344
|
+
# Check for a custom handler.
|
|
345
|
+
if path in self.custom:
|
|
346
|
+
return await self.custom[path](
|
|
347
|
+
response, request, request_data
|
|
348
|
+
)
|
|
349
|
+
|
|
338
350
|
# Try serving a file and handling redirects.
|
|
339
351
|
for handler in [self.try_redirect, self.try_file]:
|
|
340
352
|
result = await handler(
|
runtimepy/net/server/markdown.py
CHANGED
|
@@ -5,6 +5,7 @@ A module implementing web server markdown interfaces.
|
|
|
5
5
|
# built-in
|
|
6
6
|
from io import StringIO
|
|
7
7
|
import mimetypes
|
|
8
|
+
from os import stat_result
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Iterable, cast
|
|
10
11
|
|
|
@@ -17,19 +18,46 @@ from vcorelib.paths import stats as _stats
|
|
|
17
18
|
|
|
18
19
|
LOGO_MARKDOWN = "[](/)"
|
|
19
20
|
DIR_FILE = "dir.html"
|
|
21
|
+
AUTOPLAY_PREVIEW_SIZE = 100 * (1024 * 1024) # 100 MiB
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def file_preview(path: Path, link: Path) -> str:
|
|
24
|
+
def file_preview(path: Path, link: Path, stats: stat_result) -> str:
|
|
23
25
|
"""Get possible preview text for a file."""
|
|
24
26
|
|
|
25
27
|
preview = ""
|
|
26
28
|
|
|
27
|
-
if path.is_file():
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if not path.is_file():
|
|
30
|
+
return preview
|
|
31
|
+
|
|
32
|
+
mime, _ = mimetypes.guess_type(path, strict=False)
|
|
33
|
+
if mime:
|
|
34
|
+
# Image previews.
|
|
35
|
+
if mime.startswith("image"):
|
|
36
|
+
preview = div(
|
|
37
|
+
tag="img",
|
|
38
|
+
src=f"/{link}",
|
|
39
|
+
alt=str(link),
|
|
40
|
+
class_str="media-preview",
|
|
41
|
+
).encode_str(newlines=False)
|
|
42
|
+
|
|
43
|
+
# Video previews.
|
|
44
|
+
elif mime.startswith("video"):
|
|
45
|
+
elem = div(tag="video", class_str="media-preview")
|
|
46
|
+
elem.booleans.add("loop")
|
|
47
|
+
elem.booleans.add("controls")
|
|
48
|
+
|
|
49
|
+
if stats.st_size < AUTOPLAY_PREVIEW_SIZE:
|
|
50
|
+
elem.booleans.add("autoplay")
|
|
51
|
+
|
|
52
|
+
div(parent=elem, tag="source", src=f"/{link}", type=mime)
|
|
53
|
+
|
|
54
|
+
preview = elem.encode_str(newlines=False)
|
|
55
|
+
|
|
56
|
+
# Audio previews.
|
|
57
|
+
elif mime.startswith("audio"):
|
|
58
|
+
elem = div(tag="audio", src=f"/{link}")
|
|
59
|
+
elem.booleans.add("controls")
|
|
60
|
+
preview = elem.encode_str(newlines=False)
|
|
33
61
|
|
|
34
62
|
return preview
|
|
35
63
|
|
|
@@ -65,7 +93,8 @@ def write_markdown_dir(
|
|
|
65
93
|
size_str = byte_count_str(stats.st_size) if item.is_file() else ""
|
|
66
94
|
|
|
67
95
|
writer.write(
|
|
68
|
-
f"| [{name}](/{curr}) | {size_str} |
|
|
96
|
+
f"| [{name}](/{curr}) | {size_str} | "
|
|
97
|
+
f"{file_preview(item, curr, stats)} |"
|
|
69
98
|
)
|
|
70
99
|
|
|
71
100
|
writer.empty()
|
|
@@ -173,13 +173,13 @@ class HttpConnection(_TcpConnection):
|
|
|
173
173
|
) -> None:
|
|
174
174
|
"""Send a request or response to a request."""
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
header["content-length"] = "0"
|
|
178
|
-
|
|
176
|
+
size = None
|
|
179
177
|
if isinstance(data, AsyncResponse):
|
|
180
|
-
|
|
178
|
+
size = await data.size()
|
|
181
179
|
elif data is not None:
|
|
182
|
-
|
|
180
|
+
size = len(data)
|
|
181
|
+
if size is not None:
|
|
182
|
+
header["content-length"] = str(size)
|
|
183
183
|
|
|
184
184
|
self.send_binary(bytes(header))
|
|
185
185
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runtimepy
|
|
3
|
-
Version: 5.15.
|
|
3
|
+
Version: 5.15.8
|
|
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: aiofiles
|
|
21
|
-
Requires-Dist: svgen>=0.8.0
|
|
22
|
-
Requires-Dist: websockets
|
|
23
20
|
Requires-Dist: psutil
|
|
24
21
|
Requires-Dist: vcorelib>=3.6.4
|
|
22
|
+
Requires-Dist: websockets
|
|
23
|
+
Requires-Dist: svgen>=0.8.0
|
|
24
|
+
Requires-Dist: aiofiles
|
|
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.3
|
|
54
|
-
hash=
|
|
54
|
+
hash=c354cb6439439285d244ad2bdcc32cc8
|
|
55
55
|
=====================================
|
|
56
56
|
-->
|
|
57
57
|
|
|
58
|
-
# runtimepy ([5.15.
|
|
58
|
+
# runtimepy ([5.15.8](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=uc9YAPW-YaZX4q8Y53kePOKLHa19DXc_OwE9dc7py2M,391
|
|
2
2
|
runtimepy/__main__.py,sha256=IKioH2xOtsXwrwb9zABDQEJvuAX--Lnh84TeSz0XSs0,332
|
|
3
3
|
runtimepy/app.py,sha256=Er1ZKKrG9U0FV0gQg_GYF9xDb89HgYnVzS5SjxGa2Tg,970
|
|
4
4
|
runtimepy/dev_requirements.txt,sha256=VZhW6bJ5YbwaoN4d_XxZFuN5BbDLaG7ngKrGnugVPRw,245
|
|
@@ -55,7 +55,7 @@ runtimepy/data/server_dev.yaml,sha256=nQsPh7LuQig3pzHfdg_aD3yOUiCj1sKKfI-WwW3hXm
|
|
|
55
55
|
runtimepy/data/tftp_server.yaml,sha256=-bFOWJSagI-fEQQcT8k7eDMJVfSPm2XAxLVG3dqUTa4,204
|
|
56
56
|
runtimepy/data/css/bootstrap_extra.css,sha256=0NiTnZWnIYf04n9NHVe-nVSOLwePJ6X-3uCgUgAEKPI,3101
|
|
57
57
|
runtimepy/data/css/font.css,sha256=Pe82E66rNi-cwlQ-_1GHAuhPGu5L4x5KqgV0dbDe51w,977
|
|
58
|
-
runtimepy/data/css/main.css,sha256=
|
|
58
|
+
runtimepy/data/css/main.css,sha256=ohZ98-ffw6U1h2Azj8ToYiXFurQo1rH9r6kFQZkrtQ0,1355
|
|
59
59
|
runtimepy/data/js/DataConnection.js,sha256=DnX8FMehjJXqmI62UMYXSvl_XdfQMzq3XUDFbLu2GgI,98
|
|
60
60
|
runtimepy/data/js/JsonConnection.js,sha256=rclZrbmWc_zSs6I_JhOgxnVPFIyPMo5WdjAe8alyZ3o,2729
|
|
61
61
|
runtimepy/data/js/audio.js,sha256=bLkBqbeHMiGGidfL3iXjmVoF9seK-ZeZ3kwgOrcpgk4,1092
|
|
@@ -199,13 +199,13 @@ runtimepy/net/http/__init__.py,sha256=cMKNPyYZpdT03OR-HNfwzZbaKGchzlGIrK712daRK4
|
|
|
199
199
|
runtimepy/net/http/common.py,sha256=vpoO6XwRmrZmTkCu9bkI0HnyaD8MWTpV7ADesCNrfRE,2237
|
|
200
200
|
runtimepy/net/http/header.py,sha256=AECSdvhBA9_5Pg3UdwMzsmBpcqgsiPj41xnIGPm5g5E,2296
|
|
201
201
|
runtimepy/net/http/request_target.py,sha256=EfcOozUeXqOuQaMXH9cErfJqUkG0A5v9HEK4tCorSf4,1543
|
|
202
|
-
runtimepy/net/http/response.py,sha256=
|
|
202
|
+
runtimepy/net/http/response.py,sha256=rLllTaaFn3-ABMhq3jM_6-kqq8ym5VJGVWNwmBr3kAI,3823
|
|
203
203
|
runtimepy/net/http/state.py,sha256=qCMN8aWfCRfU9XP-cIhSOo2RqfljTjbQRCflfcy2bfY,1626
|
|
204
204
|
runtimepy/net/http/version.py,sha256=mp6rgIM7-VUVKLCA0Uw96CmBkL0ET860lDVVEewpZ7w,1098
|
|
205
|
-
runtimepy/net/server/__init__.py,sha256=
|
|
205
|
+
runtimepy/net/server/__init__.py,sha256=NHs8cXTRTEreVhk5QW9dMTW-pyUCd3NfuATm1a-5V6Y,12191
|
|
206
206
|
runtimepy/net/server/html.py,sha256=OB2K37kA7MHtcKqp0pZE1q_XgiyvfooigzS0-OQFCCM,1900
|
|
207
207
|
runtimepy/net/server/json.py,sha256=AaMPw-G-7xX67mf1LvQAipNdwrnmbLDunVlkWf6iOz0,2526
|
|
208
|
-
runtimepy/net/server/markdown.py,sha256=
|
|
208
|
+
runtimepy/net/server/markdown.py,sha256=kKKSGF_HwH8ADnNm5fNnWq4XXh7Td9Iu_QUj_u-qmts,3595
|
|
209
209
|
runtimepy/net/server/mux.py,sha256=ejET7NpJzzJjkVwR8laSuZYR79DOT3XazmAIm6rENSE,598
|
|
210
210
|
runtimepy/net/server/app/__init__.py,sha256=0eCcqYvmby_wQTGqAiGbGM5LCxKM2qpjuIT0gLHsoDU,3157
|
|
211
211
|
runtimepy/net/server/app/base.py,sha256=46aOqZwRss_nh_WfEH1cMJ9GUVoLJjERd7cTRFu6mXE,1878
|
|
@@ -237,7 +237,7 @@ runtimepy/net/tcp/__init__.py,sha256=OOWohegpoioSTf8M7uDf-4EV1IDungz7-U19L_2yW4I
|
|
|
237
237
|
runtimepy/net/tcp/connection.py,sha256=FL9SlODVAehECYgF9eKk8sbDMmTMBGlsGJlGaMncQh4,8793
|
|
238
238
|
runtimepy/net/tcp/create.py,sha256=zZsRs5KYpO3bNGh-DwEOEzjUDE4ixj-UBHYgZ0GvC7c,2013
|
|
239
239
|
runtimepy/net/tcp/protocol.py,sha256=vT4HOH-WLrM0j4kUpQA_ZZOwA-kJPMP9M40ptuL84_0,1412
|
|
240
|
-
runtimepy/net/tcp/http/__init__.py,sha256=
|
|
240
|
+
runtimepy/net/tcp/http/__init__.py,sha256=7Pnge4d50hG6QjcTXb1lZW1Xcver-vLVubBhj-WUduQ,6369
|
|
241
241
|
runtimepy/net/tcp/scpi/__init__.py,sha256=FcNouVQh0A27-x16lzTbt7vZfWs4bRU9uZqbeVjDfiU,2173
|
|
242
242
|
runtimepy/net/tcp/telnet/__init__.py,sha256=2pGaG-IlZA29BM6QAvI4q9w484fgvypX4fOIioDem94,4814
|
|
243
243
|
runtimepy/net/tcp/telnet/codes.py,sha256=1-yyRe-Kz_W7d6B0P3iT1AaSNR3_Twmn-MUjKCJJknY,3518
|
|
@@ -311,9 +311,9 @@ runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9Ju
|
|
|
311
311
|
runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
312
312
|
runtimepy/ui/button.py,sha256=8MXnhqmVvANwbbTA8gu3b8N4IIvRYicnwxFQD6SZy3U,2045
|
|
313
313
|
runtimepy/ui/controls.py,sha256=L55Af-4vGq6ZHewdoA7C_mAYq35WXl8NzOdcsmQIo7M,1868
|
|
314
|
-
runtimepy-5.15.
|
|
315
|
-
runtimepy-5.15.
|
|
316
|
-
runtimepy-5.15.
|
|
317
|
-
runtimepy-5.15.
|
|
318
|
-
runtimepy-5.15.
|
|
319
|
-
runtimepy-5.15.
|
|
314
|
+
runtimepy-5.15.8.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
|
|
315
|
+
runtimepy-5.15.8.dist-info/METADATA,sha256=FwB_Vl_qyjXiREOidJFwlPKEK0nCqvTrgq5ao57doHo,9268
|
|
316
|
+
runtimepy-5.15.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
317
|
+
runtimepy-5.15.8.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
|
|
318
|
+
runtimepy-5.15.8.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
|
|
319
|
+
runtimepy-5.15.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|