runtimepy 5.7.2__py3-none-any.whl → 5.7.4__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 (29) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/data/js/markdown_page.js +24 -0
  3. runtimepy/mixins/logging.py +26 -3
  4. runtimepy/net/html/__init__.py +163 -0
  5. runtimepy/net/{server/app → html}/bootstrap/elements.py +4 -2
  6. runtimepy/net/{server/app → html}/bootstrap/tabs.py +3 -24
  7. runtimepy/net/server/__init__.py +37 -7
  8. runtimepy/net/server/app/base.py +4 -11
  9. runtimepy/net/server/app/create.py +1 -1
  10. runtimepy/net/server/app/env/__init__.py +2 -5
  11. runtimepy/net/server/app/env/modal.py +2 -2
  12. runtimepy/net/server/app/env/settings.py +2 -2
  13. runtimepy/net/server/app/env/tab/base.py +1 -1
  14. runtimepy/net/server/app/env/tab/controls.py +1 -1
  15. runtimepy/net/server/app/env/tab/html.py +4 -2
  16. runtimepy/net/server/app/env/widgets.py +1 -1
  17. runtimepy/net/server/app/files.py +1 -86
  18. runtimepy/net/server/app/landing_page.py +7 -1
  19. runtimepy/net/server/app/placeholder.py +2 -2
  20. runtimepy/net/server/app/sound.py +1 -1
  21. runtimepy/net/server/app/tab.py +3 -3
  22. runtimepy/net/server/html.py +7 -5
  23. {runtimepy-5.7.2.dist-info → runtimepy-5.7.4.dist-info}/METADATA +5 -5
  24. {runtimepy-5.7.2.dist-info → runtimepy-5.7.4.dist-info}/RECORD +29 -27
  25. /runtimepy/net/{server/app → html}/bootstrap/__init__.py +0 -0
  26. {runtimepy-5.7.2.dist-info → runtimepy-5.7.4.dist-info}/LICENSE +0 -0
  27. {runtimepy-5.7.2.dist-info → runtimepy-5.7.4.dist-info}/WHEEL +0 -0
  28. {runtimepy-5.7.2.dist-info → runtimepy-5.7.4.dist-info}/entry_points.txt +0 -0
  29. {runtimepy-5.7.2.dist-info → runtimepy-5.7.4.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.1.4
4
- # hash=0a0c607a8f78dc35cf48427539c9bafa
4
+ # hash=cb3ed2d01203d78ad794d39500d706f9
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.7.2"
13
+ VERSION = "5.7.4"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -0,0 +1,24 @@
1
+ /* Dark is hard-coded initial state (in HTML). */
2
+ let lightMode = false;
3
+
4
+ function lightDarkClick(event) {
5
+ lightMode = !lightMode;
6
+
7
+ document.getElementById("runtimepy")
8
+ .setAttribute("data-bs-theme", lightMode ? "light" : "dark");
9
+
10
+ window.location.hash = lightMode ? "#light-mode" : "";
11
+ }
12
+
13
+ let lightDarkButton = document.getElementById("theme-button");
14
+ if (lightDarkButton) {
15
+ lightDarkButton.addEventListener("click", lightDarkClick);
16
+ }
17
+
18
+ if (window.location.hash) {
19
+ let parts = window.location.hash.slice(1).split(",");
20
+
21
+ if (parts.includes("light-mode")) {
22
+ lightDarkButton.click();
23
+ }
24
+ }
@@ -10,7 +10,7 @@ from typing import Any, Iterable
10
10
 
11
11
  # third-party
12
12
  import aiofiles
13
- from vcorelib.logging import LoggerMixin, LoggerType
13
+ from vcorelib.logging import ListLogger, LoggerMixin, LoggerType
14
14
  from vcorelib.paths import Pathlike, normalize
15
15
 
16
16
  # internal
@@ -66,6 +66,25 @@ class LoggerMixinLevelControl(LoggerMixin):
66
66
 
67
67
 
68
68
  LogPaths = Iterable[tuple[LogLevellike, Pathlike]]
69
+ EXT_LOG_EXTRA = {"external": True}
70
+
71
+
72
+ def handle_safe_log(
73
+ logger: LoggerType, level: int, data: str, safe_to_log: bool
74
+ ) -> None:
75
+ """handle a log filtering scenario."""
76
+
77
+ if safe_to_log:
78
+ logger.log(level, data, extra=EXT_LOG_EXTRA)
79
+ else:
80
+ record = logging.LogRecord(
81
+ logger.name, level, __file__, -1, data, (), None
82
+ )
83
+ record.external = True
84
+
85
+ for handler in logger.handlers: # type: ignore
86
+ if isinstance(handler, ListLogger):
87
+ handler.emit(record)
69
88
 
70
89
 
71
90
  class LogCaptureMixin:
@@ -76,7 +95,10 @@ class LogCaptureMixin:
76
95
  # Open aiofiles handles.
77
96
  streams: list[tuple[int, Any]]
78
97
 
79
- ext_log_extra = {"external": True}
98
+ # Set false to only forward to ListLogger handlers. Required for when the
99
+ # system log / process-management logs are being forwarded (otherwise also
100
+ # logging would lead to infinite spam).
101
+ safe_to_log = True
80
102
 
81
103
  async def init_log_capture(
82
104
  self, stack: AsyncExitStack, log_paths: LogPaths
@@ -98,7 +120,8 @@ class LogCaptureMixin:
98
120
 
99
121
  def log_line(self, level: int, data: str) -> None:
100
122
  """Log a line for output."""
101
- self.logger.log(level, data, extra=self.ext_log_extra)
123
+
124
+ handle_safe_log(self.logger, level, data, self.safe_to_log)
102
125
 
103
126
  async def dispatch_log_capture(self) -> None:
104
127
  """Get the next line from this log stream."""
@@ -0,0 +1,163 @@
1
+ """
2
+ A module implementing HTML-related interfaces.
3
+ """
4
+
5
+ # built-in
6
+ from io import StringIO
7
+ from typing import Optional
8
+
9
+ # third-party
10
+ from svgen.element import Element
11
+ from svgen.element.html import Html, div
12
+ from vcorelib import DEFAULT_ENCODING
13
+ from vcorelib.io import IndentedFileWriter
14
+ from vcorelib.paths import find_file
15
+
16
+ # internal
17
+ from runtimepy import PKG_NAME
18
+ from runtimepy.net.html.bootstrap import (
19
+ add_bootstrap_css,
20
+ add_bootstrap_js,
21
+ icon_str,
22
+ )
23
+ from runtimepy.net.html.bootstrap.elements import (
24
+ bootstrap_button,
25
+ centered_markdown,
26
+ )
27
+
28
+
29
+ def create_app_shell(parent: Element, **kwargs) -> tuple[Element, Element]:
30
+ """Create a bootstrap-based application shell."""
31
+
32
+ container = div(parent=parent, **kwargs)
33
+ container.add_class("d-flex", "align-items-start", "bg-body")
34
+
35
+ # Dark theme.
36
+ container["data-bs-theme"] = "dark"
37
+
38
+ # Buttons.
39
+ button_column = div(parent=container)
40
+ button_column.add_class("d-flex", "flex-column", "h-100", "bg-dark-subtle")
41
+
42
+ # Dark/light theme switch button.
43
+ bootstrap_button(
44
+ icon_str("lightbulb"),
45
+ tooltip=" Toggle light/dark.",
46
+ id="theme-button",
47
+ parent=button_column,
48
+ )
49
+
50
+ return container, button_column
51
+
52
+
53
+ def markdown_page(parent: Element, markdown: str, **kwargs) -> None:
54
+ """Compose a landing page."""
55
+
56
+ container = centered_markdown(
57
+ create_app_shell(parent, **kwargs)[0], markdown, "h-100", "text-body"
58
+ )
59
+ container.add_class("overflow-y-auto")
60
+
61
+
62
+ def common_css(document: Html) -> None:
63
+ """Add common CSS to an HTML document."""
64
+
65
+ append_kind(document.head, "font", kind="css", tag="style")
66
+ add_bootstrap_css(document.head)
67
+ append_kind(
68
+ document.head, "main", "bootstrap_extra", kind="css", tag="style"
69
+ )
70
+
71
+
72
+ def full_markdown_page(document: Html, markdown: str) -> None:
73
+ """Render a full markdown HTML app."""
74
+
75
+ common_css(document)
76
+ markdown_page(document.body, markdown, id=PKG_NAME)
77
+
78
+ # JavaScript.
79
+ append_kind(document.body, "markdown_page")
80
+ add_bootstrap_js(document.body)
81
+
82
+
83
+ def handle_worker(writer: IndentedFileWriter) -> int:
84
+ """Boilerplate contents for worker thread block."""
85
+
86
+ # Not currently used.
87
+ # return write_found_file(
88
+ # writer, kind_url("js", "webgl-debug", subdir="third-party")
89
+ # )
90
+ del writer
91
+
92
+ return 0
93
+
94
+
95
+ def write_found_file(writer: IndentedFileWriter, *args, **kwargs) -> bool:
96
+ """Write a file's contents to the file-writer's stream."""
97
+
98
+ result = False
99
+
100
+ entry = find_file(*args, **kwargs)
101
+ if entry is not None:
102
+ with entry.open(encoding=DEFAULT_ENCODING) as path_fd:
103
+ for line in path_fd:
104
+ writer.write(line)
105
+
106
+ result = True
107
+
108
+ return result
109
+
110
+
111
+ def kind_url(
112
+ kind: str, name: str, subdir: str = None, package: str = PKG_NAME
113
+ ) -> str:
114
+ """Return a URL to find a package resource."""
115
+
116
+ path = kind
117
+
118
+ if subdir is not None:
119
+ path += "/" + subdir
120
+
121
+ path += f"/{name}"
122
+
123
+ return f"package://{package}/{path}.{kind}"
124
+
125
+
126
+ WORKER_TYPE = "text/js-worker"
127
+
128
+
129
+ def append_kind(
130
+ element: Element,
131
+ *names: str,
132
+ package: str = PKG_NAME,
133
+ kind: str = "js",
134
+ tag: str = "script",
135
+ subdir: str = None,
136
+ worker: bool = False,
137
+ ) -> Optional[Element]:
138
+ """Append a new script element."""
139
+
140
+ elem = Element(tag=tag, allow_no_end_tag=False)
141
+
142
+ with StringIO() as stream:
143
+ writer = IndentedFileWriter(stream, per_indent=2)
144
+ found_count = 0
145
+ for name in names:
146
+ if write_found_file(
147
+ writer, kind_url(kind, name, subdir=subdir, package=package)
148
+ ):
149
+ found_count += 1
150
+
151
+ if worker:
152
+ found_count += handle_worker(writer)
153
+
154
+ if found_count:
155
+ elem.text = stream.getvalue()
156
+
157
+ if found_count:
158
+ element.children.append(elem)
159
+
160
+ if worker:
161
+ elem["type"] = WORKER_TYPE
162
+
163
+ return elem if found_count else None
@@ -12,7 +12,7 @@ from svgen.element.html import div
12
12
  from vcorelib.io.file_writer import IndentedFileWriter
13
13
 
14
14
  # internal
15
- from runtimepy.net.server.app.bootstrap import icon_str
15
+ from runtimepy.net.html.bootstrap import icon_str
16
16
 
17
17
  TEXT = "font-monospace"
18
18
  BOOTSTRAP_BUTTON = f"rounded-0 {TEXT} button-bodge text-nowrap"
@@ -167,7 +167,7 @@ def slider(
167
167
 
168
168
  def centered_markdown(
169
169
  parent: Element, markdown: str, *container_classes: str
170
- ) -> None:
170
+ ) -> Element:
171
171
  """Add centered markdown."""
172
172
 
173
173
  container = div(parent=parent)
@@ -198,3 +198,5 @@ def centered_markdown(
198
198
  div(parent=horiz_container)
199
199
 
200
200
  div(parent=container)
201
+
202
+ return container
@@ -8,10 +8,9 @@ from svgen.element.html import div
8
8
 
9
9
  # internal
10
10
  from runtimepy import PKG_NAME
11
- from runtimepy.net.server.app.bootstrap import icon_str
12
- from runtimepy.net.server.app.bootstrap.elements import (
11
+ from runtimepy.net.html import create_app_shell
12
+ from runtimepy.net.html.bootstrap.elements import (
13
13
  BOOTSTRAP_BUTTON,
14
- bootstrap_button,
15
14
  collapse_button,
16
15
  flex,
17
16
  toggle_button,
@@ -84,27 +83,7 @@ class TabbedContent:
84
83
  """Initialize this instance."""
85
84
 
86
85
  self.name = name
87
-
88
- # Create application container.
89
- self.container = div(parent=parent, id=name)
90
- self.container.add_class("d-flex", "align-items-start", "bg-body")
91
-
92
- # Dark theme.
93
- self.container["data-bs-theme"] = "dark"
94
-
95
- # Buttons.
96
- self.button_column = div(parent=self.container)
97
- self.button_column.add_class(
98
- "d-flex", "flex-column", "h-100", "bg-dark-subtle"
99
- )
100
-
101
- # Dark/light theme switch button.
102
- bootstrap_button(
103
- icon_str("lightbulb"),
104
- tooltip=" Toggle light/dark.",
105
- id="theme-button",
106
- parent=self.button_column,
107
- )
86
+ self.container, self.button_column = create_app_shell(parent, id=name)
108
87
 
109
88
  # Toggle tabs button.
110
89
  self.add_button("Toggle tabs", f"#{PKG_NAME}-tabs", id="tabs-button")
@@ -11,16 +11,19 @@ from pathlib import Path
11
11
  from typing import Any, Optional, TextIO, Union
12
12
 
13
13
  # third-party
14
- from vcorelib.io import JsonObject
14
+ import aiofiles
15
+ from vcorelib import DEFAULT_ENCODING
16
+ from vcorelib.io import IndentedFileWriter, JsonObject
15
17
  from vcorelib.paths import Pathlike, find_file, normalize
16
18
 
17
19
  # internal
18
20
  from runtimepy import DEFAULT_EXT, PKG_NAME
19
21
  from runtimepy.channel.environment.command import GLOBAL
22
+ from runtimepy.net.html import full_markdown_page
20
23
  from runtimepy.net.http.header import RequestHeader
21
24
  from runtimepy.net.http.request_target import PathMaybeQuery
22
25
  from runtimepy.net.http.response import ResponseHeader
23
- from runtimepy.net.server.html import HtmlApp, HtmlApps, html_handler
26
+ from runtimepy.net.server.html import HtmlApp, HtmlApps, get_html, html_handler
24
27
  from runtimepy.net.server.json import encode_json, json_handler
25
28
  from runtimepy.net.tcp.http import HttpConnection
26
29
  from runtimepy.util import normalize_root, path_has_part
@@ -106,7 +109,7 @@ class RuntimepyServerConnection(HttpConnection):
106
109
  with favicon.open("rb") as favicon_fd:
107
110
  type(self).favicon_data = favicon_fd.read()
108
111
 
109
- def try_redirect(
112
+ async def try_redirect(
110
113
  self, path: PathMaybeQuery, response: ResponseHeader
111
114
  ) -> Optional[bytes]:
112
115
  """Try handling any HTTP redirect rules."""
@@ -123,7 +126,26 @@ class RuntimepyServerConnection(HttpConnection):
123
126
 
124
127
  return result
125
128
 
126
- def try_file(
129
+ async def render_markdown(
130
+ self, path: Path, response: ResponseHeader, **kwargs
131
+ ) -> bytes:
132
+ """Render a markdown file as HTML and return the result."""
133
+
134
+ document = get_html()
135
+
136
+ async with aiofiles.open(path, mode="r") as path_fd:
137
+ with IndentedFileWriter.string() as writer:
138
+ writer.write_markdown(await path_fd.read(), **kwargs)
139
+ full_markdown_page(
140
+ document,
141
+ writer.stream.getvalue(), # type: ignore
142
+ )
143
+
144
+ response["Content-Type"] = f"text/html; charset={DEFAULT_ENCODING}"
145
+
146
+ return document.encode_str().encode()
147
+
148
+ async def try_file(
127
149
  self, path: PathMaybeQuery, response: ResponseHeader
128
150
  ) -> Optional[bytes]:
129
151
  """Try serving this path as a file directly from the file-system."""
@@ -133,6 +155,12 @@ class RuntimepyServerConnection(HttpConnection):
133
155
  # Try serving the path as a file.
134
156
  for search in self.paths:
135
157
  candidate = search.joinpath(path[0][1:])
158
+
159
+ # Handle markdown sources.
160
+ md_candidate = candidate.with_suffix(".md")
161
+ if md_candidate.is_file():
162
+ return await self.render_markdown(md_candidate, response)
163
+
136
164
  if candidate.is_file():
137
165
  mime, encoding = mimetypes.guess_type(candidate, strict=False)
138
166
 
@@ -146,8 +174,8 @@ class RuntimepyServerConnection(HttpConnection):
146
174
  self.logger.info("Serving '%s' (MIME: %s)", candidate, mime)
147
175
 
148
176
  # Return the file data.
149
- with candidate.open("rb") as path_fd:
150
- result = path_fd.read()
177
+ async with aiofiles.open(candidate, mode="rb") as path_fd:
178
+ result = await path_fd.read()
151
179
 
152
180
  break
153
181
 
@@ -223,7 +251,9 @@ class RuntimepyServerConnection(HttpConnection):
223
251
 
224
252
  # Try serving a file and handling redirects.
225
253
  for handler in [self.try_redirect, self.try_file]:
226
- result = handler(request.target.origin_form, response)
254
+ result = await handler(
255
+ request.target.origin_form, response
256
+ )
227
257
  if result is not None:
228
258
  return result
229
259
 
@@ -11,12 +11,9 @@ from svgen.element.html import Html
11
11
  # internal
12
12
  from runtimepy import PKG_NAME
13
13
  from runtimepy.net.arbiter.info import AppInfo
14
- from runtimepy.net.server.app.bootstrap import (
15
- add_bootstrap_css,
16
- add_bootstrap_js,
17
- )
18
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
19
- from runtimepy.net.server.app.files import append_kind
14
+ from runtimepy.net.html import append_kind, common_css
15
+ from runtimepy.net.html.bootstrap import add_bootstrap_js
16
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
20
17
 
21
18
  TabPopulater = Callable[[TabbedContent], None]
22
19
 
@@ -53,11 +50,7 @@ class WebApplication:
53
50
  """Populate the body element with the application."""
54
51
 
55
52
  # CSS.
56
- append_kind(document.head, "font", kind="css", tag="style")
57
- add_bootstrap_css(document.head)
58
- append_kind(
59
- document.head, "main", "bootstrap_extra", kind="css", tag="style"
60
- )
53
+ common_css(document)
61
54
 
62
55
  # Worker code.
63
56
  append_kind(
@@ -11,10 +11,10 @@ from vcorelib.logging import LoggerMixin
11
11
 
12
12
  # internal
13
13
  from runtimepy.net.arbiter.info import AppInfo
14
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
14
15
  from runtimepy.net.http.header import RequestHeader
15
16
  from runtimepy.net.http.response import ResponseHeader
16
17
  from runtimepy.net.server.app.base import WebApplication
17
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
18
18
  from runtimepy.net.server.html import HtmlApp
19
19
 
20
20
  DOCUMENTS: dict[str, Html] = {}
@@ -8,11 +8,8 @@ from svgen.element.html import div
8
8
  # internal
9
9
  from runtimepy import PKG_NAME
10
10
  from runtimepy.net.arbiter.info import AppInfo
11
- from runtimepy.net.server.app.bootstrap.elements import (
12
- centered_markdown,
13
- input_box,
14
- )
15
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
11
+ from runtimepy.net.html.bootstrap.elements import centered_markdown, input_box
12
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
16
13
  from runtimepy.net.server.app.env.modal import Modal
17
14
  from runtimepy.net.server.app.env.settings import plot_settings
18
15
  from runtimepy.net.server.app.env.tab import ChannelEnvironmentTab
@@ -7,8 +7,8 @@ from svgen.element.html import div
7
7
 
8
8
  # internal
9
9
  from runtimepy import PKG_NAME
10
- from runtimepy.net.server.app.bootstrap.elements import TEXT
11
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
10
+ from runtimepy.net.html.bootstrap.elements import TEXT
11
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
12
12
 
13
13
 
14
14
  class Modal:
@@ -6,8 +6,8 @@ A module implementing an application-settings modal.
6
6
  from svgen.element.html import div
7
7
 
8
8
  # internal
9
- from runtimepy.net.server.app.bootstrap.elements import flex, slider
10
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
9
+ from runtimepy.net.html.bootstrap.elements import flex, slider
10
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
11
11
  from runtimepy.net.server.app.env.modal import Modal
12
12
  from runtimepy.net.server.app.placeholder import under_construction
13
13
 
@@ -13,7 +13,7 @@ from runtimepy.channel.environment.command.processor import (
13
13
  ChannelCommandProcessor,
14
14
  )
15
15
  from runtimepy.net.arbiter.info import AppInfo
16
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
16
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
17
17
  from runtimepy.net.server.app.tab import Tab
18
18
 
19
19
 
@@ -13,7 +13,7 @@ from svgen.element.html import div
13
13
  from runtimepy.channel import AnyChannel
14
14
  from runtimepy.channel.environment import ChannelEnvironment
15
15
  from runtimepy.enum import RuntimeEnum
16
- from runtimepy.net.server.app.bootstrap.elements import slider, toggle_button
16
+ from runtimepy.net.html.bootstrap.elements import slider, toggle_button
17
17
  from runtimepy.net.server.app.env.tab.base import ChannelEnvironmentTabBase
18
18
  from runtimepy.net.server.app.env.widgets import (
19
19
  TABLE_BUTTON_CLASSES,
@@ -12,7 +12,7 @@ from svgen.element.html import div
12
12
  # internal
13
13
  from runtimepy.channel import AnyChannel
14
14
  from runtimepy.enum import RuntimeEnum
15
- from runtimepy.net.server.app.bootstrap.elements import (
15
+ from runtimepy.net.html.bootstrap.elements import (
16
16
  TEXT,
17
17
  centered_markdown,
18
18
  flex,
@@ -241,7 +241,9 @@ class ChannelEnvironmentTabHtml(ChannelEnvironmentTabControls):
241
241
  logs = div(
242
242
  tag="textarea",
243
243
  parent=div(parent=vert_container, class_str="form-floating"),
244
- class_str=(f"form-control rounded-0 {TEXT} text-logs"),
244
+ class_str=(
245
+ f"form-control rounded-0 {TEXT} text-body-emphasis text-logs"
246
+ ),
245
247
  id=self.get_id("logs"),
246
248
  title=f"Text logs for {self.name}.",
247
249
  )
@@ -14,7 +14,7 @@ from runtimepy.channel.environment.command.processor import (
14
14
  ChannelCommandProcessor,
15
15
  )
16
16
  from runtimepy.enum import RuntimeEnum
17
- from runtimepy.net.server.app.bootstrap.elements import (
17
+ from runtimepy.net.html.bootstrap.elements import (
18
18
  flex,
19
19
  input_box,
20
20
  set_tooltip,
@@ -4,32 +4,14 @@ A module implementing interfaces for working with file contents.
4
4
 
5
5
  # built-in
6
6
  from io import StringIO
7
- from typing import Optional
8
7
 
9
8
  # third-party
10
9
  from svgen.element import Element
11
- from vcorelib import DEFAULT_ENCODING
12
10
  from vcorelib.io import IndentedFileWriter
13
- from vcorelib.paths import find_file
14
11
 
15
12
  # internal
16
13
  from runtimepy import PKG_NAME
17
-
18
-
19
- def write_found_file(writer: IndentedFileWriter, *args, **kwargs) -> bool:
20
- """Write a file's contents to the file-writer's stream."""
21
-
22
- result = False
23
-
24
- entry = find_file(*args, **kwargs)
25
- if entry is not None:
26
- with entry.open(encoding=DEFAULT_ENCODING) as path_fd:
27
- for line in path_fd:
28
- writer.write(line)
29
-
30
- result = True
31
-
32
- return result
14
+ from runtimepy.net.html import kind_url, write_found_file
33
15
 
34
16
 
35
17
  def set_text_to_file(element: Element, *args, **kwargs) -> bool:
@@ -45,21 +27,6 @@ def set_text_to_file(element: Element, *args, **kwargs) -> bool:
45
27
  return result
46
28
 
47
29
 
48
- def kind_url(
49
- kind: str, name: str, subdir: str = None, package: str = PKG_NAME
50
- ) -> str:
51
- """Return a URL to find a package resource."""
52
-
53
- path = kind
54
-
55
- if subdir is not None:
56
- path += "/" + subdir
57
-
58
- path += f"/{name}"
59
-
60
- return f"package://{package}/{path}.{kind}"
61
-
62
-
63
30
  def set_text_to_kind(
64
31
  element: Element,
65
32
  kind: str,
@@ -72,55 +39,3 @@ def set_text_to_kind(
72
39
  return set_text_to_file(
73
40
  element, kind_url(kind, name, subdir=subdir, package=package)
74
41
  )
75
-
76
-
77
- WORKER_TYPE = "text/js-worker"
78
-
79
-
80
- def handle_worker(writer: IndentedFileWriter) -> int:
81
- """Boilerplate contents for worker thread block."""
82
-
83
- # Not currently used.
84
- # return write_found_file(
85
- # writer, kind_url("js", "webgl-debug", subdir="third-party")
86
- # )
87
- del writer
88
-
89
- return 0
90
-
91
-
92
- def append_kind(
93
- element: Element,
94
- *names: str,
95
- package: str = PKG_NAME,
96
- kind: str = "js",
97
- tag: str = "script",
98
- subdir: str = None,
99
- worker: bool = False,
100
- ) -> Optional[Element]:
101
- """Append a new script element."""
102
-
103
- elem = Element(tag=tag, allow_no_end_tag=False)
104
-
105
- with StringIO() as stream:
106
- writer = IndentedFileWriter(stream, per_indent=2)
107
- found_count = 0
108
- for name in names:
109
- if write_found_file(
110
- writer, kind_url(kind, name, subdir=subdir, package=package)
111
- ):
112
- found_count += 1
113
-
114
- if worker:
115
- found_count += handle_worker(writer)
116
-
117
- if found_count:
118
- elem.text = stream.getvalue()
119
-
120
- if found_count:
121
- element.children.append(elem)
122
-
123
- if worker:
124
- elem["type"] = WORKER_TYPE
125
-
126
- return elem if found_count else None
@@ -10,6 +10,7 @@ from svgen.element.html import Html
10
10
 
11
11
  # internal
12
12
  from runtimepy.net.arbiter.info import AppInfo
13
+ from runtimepy.net.html import full_markdown_page
13
14
  from runtimepy.net.http.header import RequestHeader
14
15
  from runtimepy.net.http.response import ResponseHeader
15
16
 
@@ -28,6 +29,11 @@ def landing_page(
28
29
  del response
29
30
  del request_data
30
31
 
31
- print(app)
32
+ full_markdown_page(
33
+ document,
34
+ app.config_param("landing_page", {}, strict=True).get( # type: ignore
35
+ "markdown", "no data"
36
+ ),
37
+ )
32
38
 
33
39
  return document
@@ -8,8 +8,8 @@ from svgen.element.html import div
8
8
 
9
9
  # internal
10
10
  from runtimepy.net.arbiter.info import AppInfo
11
- from runtimepy.net.server.app.bootstrap import icon_str
12
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
11
+ from runtimepy.net.html.bootstrap import icon_str
12
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
13
13
  from runtimepy.net.server.app.tab import Tab
14
14
 
15
15
 
@@ -7,7 +7,7 @@ from svgen.element import Element
7
7
  from svgen.element.html import div
8
8
 
9
9
  # built-in
10
- from runtimepy.net.server.app.bootstrap.elements import bootstrap_button
10
+ from runtimepy.net.html.bootstrap.elements import bootstrap_button
11
11
  from runtimepy.net.server.app.tab import Tab
12
12
 
13
13
 
@@ -13,9 +13,9 @@ from vcorelib.io.file_writer import IndentedFileWriter
13
13
 
14
14
  # internal
15
15
  from runtimepy.net.arbiter.info import AppInfo
16
- from runtimepy.net.server.app.bootstrap import icon_str
17
- from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
18
- from runtimepy.net.server.app.files import kind_url, write_found_file
16
+ from runtimepy.net.html import kind_url, write_found_file
17
+ from runtimepy.net.html.bootstrap import icon_str
18
+ from runtimepy.net.html.bootstrap.tabs import TabbedContent
19
19
 
20
20
 
21
21
  class Tab:
@@ -20,6 +20,12 @@ HtmlApp = Callable[
20
20
  HtmlApps = dict[str, HtmlApp]
21
21
 
22
22
 
23
+ def get_html() -> Html:
24
+ """Get a default HTML document."""
25
+
26
+ return Html(HttpConnection.identity)
27
+
28
+
23
29
  async def html_handler(
24
30
  apps: HtmlApps,
25
31
  stream: TextIO,
@@ -36,8 +42,4 @@ async def html_handler(
36
42
  # Create the application.
37
43
  app = apps.get(request.target.path, default_app)
38
44
  if app is not None:
39
- (
40
- await app(
41
- Html(HttpConnection.identity), request, response, request_data
42
- )
43
- ).render(stream)
45
+ (await app(get_html(), request, response, request_data)).render(stream)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: runtimepy
3
- Version: 5.7.2
3
+ Version: 5.7.4
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/vkottler/runtimepy
6
6
  Author: Vaughn Kottler
@@ -17,10 +17,10 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: vcorelib >=3.4.2
21
- Requires-Dist: websockets
22
20
  Requires-Dist: aiofiles
23
21
  Requires-Dist: psutil
22
+ Requires-Dist: websockets
23
+ Requires-Dist: vcorelib >=3.4.2
24
24
  Requires-Dist: svgen >=0.6.8
25
25
  Provides-Extra: test
26
26
  Requires-Dist: pylint ; extra == 'test'
@@ -45,11 +45,11 @@ Requires-Dist: uvloop ; (sys_platform != "win32" and sys_platform != "cygwin") a
45
45
  =====================================
46
46
  generator=datazen
47
47
  version=3.1.4
48
- hash=618546df79dd7f387faa6f84f9a58c79
48
+ hash=fbb71900eae64b008d2a2df1b7dde92b
49
49
  =====================================
50
50
  -->
51
51
 
52
- # runtimepy ([5.7.2](https://pypi.org/project/runtimepy/))
52
+ # runtimepy ([5.7.4](https://pypi.org/project/runtimepy/))
53
53
 
54
54
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
55
55
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -1,4 +1,4 @@
1
- runtimepy/__init__.py,sha256=52lFAvkdOBUthOa7tJ66FInklDlbnSn3q9BoqWKadlY,390
1
+ runtimepy/__init__.py,sha256=PWwxhEEdCroFpm1bLs0ARQ08yoplDS1rmSMU3uDL-P4,390
2
2
  runtimepy/__main__.py,sha256=OPAed6hggoQdw-6QAR62mqLC-rCkdDhOq0wyeS2vDRI,332
3
3
  runtimepy/app.py,sha256=sTvatbsGZ2Hdel36Si_WUbNMtg9CzsJyExr5xjIcxDE,970
4
4
  runtimepy/dev_requirements.txt,sha256=j0dh11ztJAzfaUL0iFheGjaZj9ppDzmTkclTT8YKO8c,230
@@ -59,6 +59,7 @@ runtimepy/data/js/audio.js,sha256=bLkBqbeHMiGGidfL3iXjmVoF9seK-ZeZ3kwgOrcpgk4,10
59
59
  runtimepy/data/js/events.js,sha256=rgz3Q_8J6sfU_7Sa7fG1mZD0pQ4S3vwN2mqcvQfePkM,554
60
60
  runtimepy/data/js/init.js,sha256=IeFqfab7CM2-Z4fIbyGaUD4M2orUT8uLwcVlleQqXzg,1522
61
61
  runtimepy/data/js/main.js,sha256=nYIQ6O76EWqlzwX7oEwPXqC-LCUFCZYDADK9QbYRDKk,404
62
+ runtimepy/data/js/markdown_page.js,sha256=pygLkO6P1AJnOy5PjIUH_px1OEiROrSNLGLUFuERMJc,610
62
63
  runtimepy/data/js/util.js,sha256=Xc8pHUiFDBDvIqTamWrCYUOpF7iR9VNvPDCSCQAfLDA,1424
63
64
  runtimepy/data/js/worker.js,sha256=V9deGAynjvUr1D-WGi3wUW8rxoaNLvBvayMoLFZk3w0,2444
64
65
  runtimepy/data/js/classes/App.js,sha256=nnY42Q3tlNzf8JZtuGKyxJZLLNMfResdww8svOQMC3U,3402
@@ -131,7 +132,7 @@ runtimepy/mixins/async_command.py,sha256=xZNTiRo_Kd_aJboLuFOkQyMo2Exn8ENyY483zdw
131
132
  runtimepy/mixins/enum.py,sha256=IRQR7HD8J0uoxahNqb-2LIVokFn1L2-v9I5HNFw9W3s,719
132
133
  runtimepy/mixins/environment.py,sha256=9dCy7nL1bwZY1p-ez8zy9Jf_6zhljSESnK9F9wtRdl8,3955
133
134
  runtimepy/mixins/finalize.py,sha256=pTziopAWXpyRy0I8UZZv8Q4abXvnH3RrQ_dt3Rh6xlA,1600
134
- runtimepy/mixins/logging.py,sha256=BS1TQTNED-e1rsexhuT2kubP_44MwpiiTSqCgzw56mQ,2991
135
+ runtimepy/mixins/logging.py,sha256=Z5zTIImkkwK0BVhm-4hEP6dzq_8pRkC9wYBCkRskmlc,3735
135
136
  runtimepy/mixins/psutil.py,sha256=RlaEuyQ0-TynQxYW85vFuPiQ-29T2mIrsdaDlpSJq1s,1293
136
137
  runtimepy/mixins/regex.py,sha256=kpCj4iL1akzt_KPPiMP-bTbuLBHOpkUrwbCTRe8HSUk,1061
137
138
  runtimepy/mixins/trig.py,sha256=vkDd9Rh8080gvH5OHkSYmjImVgM_ZZ7RzMxsczKx5N4,2480
@@ -164,6 +165,10 @@ runtimepy/net/arbiter/struct/__init__.py,sha256=Vr38dp2X0PZOrAbjKsZ9xZdQ1j3z92s4
164
165
  runtimepy/net/arbiter/tcp/__init__.py,sha256=djNm8il_9aLNpGsYResJlFmyIqx9XNLqVay-mYnn8vc,1530
165
166
  runtimepy/net/arbiter/tcp/json.py,sha256=W9a_OwBPmIoB2XZf4iuAIWQhMg2qA9xejBhGBdNCPnI,742
166
167
  runtimepy/net/factories/__init__.py,sha256=rPdBVpgzzQYF61w6efQrEre71yMPHd6kanBpMdOX-3c,4672
168
+ runtimepy/net/html/__init__.py,sha256=BQeGpZWfGSmMg1R0N9GaOZfyFn4UQGHKoFOhd-1G75Q,3983
169
+ runtimepy/net/html/bootstrap/__init__.py,sha256=ONhwx68piWjsrf88FMpda84TWSPqgi-RZCBuWCci_ak,1444
170
+ runtimepy/net/html/bootstrap/elements.py,sha256=8sq79EPR8TF9oYV_4I0Sba_PikHOpEaxUvQ7bqtm0ow,4800
171
+ runtimepy/net/html/bootstrap/tabs.py,sha256=7-0lJOD9SALmsViUYGJAXF0PTSqtffc4mpoFwmYcnXE,3842
167
172
  runtimepy/net/http/__init__.py,sha256=4TjFp_ajAVcOEvwtjlF6mG-9EbEePqFZht-QpWIKVBo,1802
168
173
  runtimepy/net/http/common.py,sha256=vpoO6XwRmrZmTkCu9bkI0HnyaD8MWTpV7ADesCNrfRE,2237
169
174
  runtimepy/net/http/header.py,sha256=AECSdvhBA9_5Pg3UdwMzsmBpcqgsiPj41xnIGPm5g5E,2296
@@ -171,30 +176,27 @@ runtimepy/net/http/request_target.py,sha256=EE1aI5VSARw1h93jyZvP56ir5O5fjd6orYK-
171
176
  runtimepy/net/http/response.py,sha256=Sup8W_A0ADNzR5olKrQsVNhsQXUwPOD-eJLlLOgYlAY,2316
172
177
  runtimepy/net/http/state.py,sha256=qCMN8aWfCRfU9XP-cIhSOo2RqfljTjbQRCflfcy2bfY,1626
173
178
  runtimepy/net/http/version.py,sha256=mp6rgIM7-VUVKLCA0Uw96CmBkL0ET860lDVVEewpZ7w,1098
174
- runtimepy/net/server/__init__.py,sha256=sxAOQc6jywBYqqZOCuq_jRJMVoA0ZFaFy2hDpgoOtSs,7845
175
- runtimepy/net/server/html.py,sha256=xaTGelH4zrwndQjU24kbCj9Yqu-D17nK5682P6xa-cU,1153
179
+ runtimepy/net/server/__init__.py,sha256=Yt2ltmiQDkVNjvYCqFh1kHlK0eelWYVHwQNkJxozta0,8946
180
+ runtimepy/net/server/html.py,sha256=dlVqIEDTdAhU7NK84sTqXqGxHpqndsWS36Kgzy9MJdE,1189
176
181
  runtimepy/net/server/json.py,sha256=a7vM5yfq2er4DexzFqEMnxoMGDeuywKkVH4-uNJBAik,2522
177
182
  runtimepy/net/server/app/__init__.py,sha256=Q5NWlAkIZE2oEHkGdILFyQoX1Fw1lMR1lVQyMVkJ_Hc,2989
178
- runtimepy/net/server/app/base.py,sha256=9PnonII2qZIf1O9BQ8T-wOAgAVHctpLqyZkWGX2M2r4,2104
179
- runtimepy/net/server/app/create.py,sha256=N-g3kClBsG4pKOd9tx947rOq4sfgrH_FAMVfZacjhFA,2666
183
+ runtimepy/net/server/app/base.py,sha256=HF_Qa3ufrZNaYBVnBwGi-Siv3nneqEo01j5h5pK-ZTk,1871
184
+ runtimepy/net/server/app/create.py,sha256=eRT8qxubht5A7149Xol3Z8rkdYd_pjNLqrlyMnXk-Zg,2660
180
185
  runtimepy/net/server/app/elements.py,sha256=KJt9vWqkfvniJMiLOJN467JjPPrEqJYZXmDuY1JoY1g,455
181
- runtimepy/net/server/app/files.py,sha256=cIHtIKqaVvOpKL62LSVV1IRPdUyCRCg0TOX9XXmYtqk,2903
182
- runtimepy/net/server/app/landing_page.py,sha256=axHGBeFpjn5PF87Yb-n73me4T9b8pQxBq4zlvX5CjmE,663
183
- runtimepy/net/server/app/placeholder.py,sha256=BDoLtS6dSeLUKnUZe3z17pb5QuXH4C69RqoIpEc3BIY,1676
186
+ runtimepy/net/server/app/files.py,sha256=9EiGA5aYczJN6BJifAIZ8e3wBbIGCekVLCu0D54ZAFc,968
187
+ runtimepy/net/server/app/landing_page.py,sha256=nO7uIO30Sw3R41qH8I1q9RWk1RFLDy8JhlrkA3avTQQ,870
188
+ runtimepy/net/server/app/placeholder.py,sha256=OuiMNvLEK0pI2N3oIqvPRaperoJkDg-fBds9HHfuosg,1664
184
189
  runtimepy/net/server/app/pyodide.py,sha256=dGJ4u78eYQLU9RQbZEWgzv-7jVIwblS6VopibG82tA4,478
185
- runtimepy/net/server/app/sound.py,sha256=AeJoa6XKNH3TYvH6OuAwyfJ5CyFJJwCF0zeCMIpLZAE,843
186
- runtimepy/net/server/app/tab.py,sha256=4jhw71LfA23wT9m9e5ofw-tBXYW6xky4tGwBOlGkOz8,2296
187
- runtimepy/net/server/app/bootstrap/__init__.py,sha256=ONhwx68piWjsrf88FMpda84TWSPqgi-RZCBuWCci_ak,1444
188
- runtimepy/net/server/app/bootstrap/elements.py,sha256=otKJGuDVNL7yKpjdIWZw2fyiHVlRRBPdgRrtRjOW2Vk,4781
189
- runtimepy/net/server/app/bootstrap/tabs.py,sha256=tZHgG2NuJ0kJjNjgzCaQEwpuZywRy0bAEEtgg6aB-ns,4454
190
- runtimepy/net/server/app/env/__init__.py,sha256=WGgIaB4cg4zqEc7BOCvtNEDvCYWyg48tVHtJiDdHtMA,4172
191
- runtimepy/net/server/app/env/modal.py,sha256=BDnZvdzwu68ZCKISab39n8_NRWh518IyrtObovhC8SU,1752
192
- runtimepy/net/server/app/env/settings.py,sha256=2dJydD7_lH8ae43d0D28BNRCbUjmmq4gc-eEAqDOCHs,1732
193
- runtimepy/net/server/app/env/widgets.py,sha256=_kNvPl7MXnZOiwTjoZiU2hfuSjkLnRUrORTVDi3w7Ls,5312
190
+ runtimepy/net/server/app/sound.py,sha256=qG2k00nWRPAmI5_4L_LMs10nsCTBKaAzIp80embgua8,837
191
+ runtimepy/net/server/app/tab.py,sha256=gRiaUJdB3V9hIKI5MksW7spNChfMtEMDFWCTP1ERrZ4,2272
192
+ runtimepy/net/server/app/env/__init__.py,sha256=_FcgefASOwifwT0m5opg8jQbpZAVoedhNxWw0v1VB1k,4147
193
+ runtimepy/net/server/app/env/modal.py,sha256=HTipCYgQaAUtsmlBjXKfhAM5JyhLqoIIwEwsPnKhrG8,1740
194
+ runtimepy/net/server/app/env/settings.py,sha256=DboR8vXrdGeB_ehP9USvnyUtzgo4JR5CyYV9AGLYHGI,1720
195
+ runtimepy/net/server/app/env/widgets.py,sha256=ccrkJNikL5VEKDaTMd8FSI9VzjYYHcpuIRdDjvpQtug,5306
194
196
  runtimepy/net/server/app/env/tab/__init__.py,sha256=stTVKyHljLQWnnhxkWPwa7bLdZtjhiMFbiVFgbiYaFI,647
195
- runtimepy/net/server/app/env/tab/base.py,sha256=hcHKG17JGkAZjl2X7jT1B72RHTeMdocTakFyNKkmWJc,1168
196
- runtimepy/net/server/app/env/tab/controls.py,sha256=hsj0ErywfmOBtJLNZbMISrE2ELJEfyXvtCtpsDbXlYg,4734
197
- runtimepy/net/server/app/env/tab/html.py,sha256=wjG_hhuJVeN4xWpGTyQF-TLKmMxc31nfe0zU4wU99S8,7130
197
+ runtimepy/net/server/app/env/tab/base.py,sha256=v5-Zbo_NqRqoLuceYclz348oFHcL68T2mbEh4EDWtTg,1162
198
+ runtimepy/net/server/app/env/tab/controls.py,sha256=bifFMvshDNsar3UiqeXOCYi71JGFRlMnyixKXrdLAEI,4728
199
+ runtimepy/net/server/app/env/tab/html.py,sha256=IfKCQ_2qupIRtWIuodIBM8XAal1esSXovht1SeGyvb4,7173
198
200
  runtimepy/net/server/app/env/tab/message.py,sha256=OWqBIPRt6UdBHAXk5qvFMbnWuvIRUyp34lzb3GYdthk,4070
199
201
  runtimepy/net/server/struct/__init__.py,sha256=Zy37r6RLFu-XFr9vsanSq80BJdS6Dxr7zmPzQbb7xdw,1799
200
202
  runtimepy/net/server/websocket/__init__.py,sha256=KISuFUUQwNn6BXo8BOMuMOXyoVqE7Jw94ZQiSCQuRQE,5279
@@ -280,9 +282,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
280
282
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
281
283
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
282
284
  runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
283
- runtimepy-5.7.2.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
284
- runtimepy-5.7.2.dist-info/METADATA,sha256=alklDGCCJxGdAWxIyShsmg-yZJlbN_arrXDTnMG3QaQ,9308
285
- runtimepy-5.7.2.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
286
- runtimepy-5.7.2.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
287
- runtimepy-5.7.2.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
288
- runtimepy-5.7.2.dist-info/RECORD,,
285
+ runtimepy-5.7.4.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
286
+ runtimepy-5.7.4.dist-info/METADATA,sha256=TN5nOZ_PyKZP_k6k5UW13hOrSEGKpgthVPvjw3h0GRs,9308
287
+ runtimepy-5.7.4.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
288
+ runtimepy-5.7.4.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
289
+ runtimepy-5.7.4.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
290
+ runtimepy-5.7.4.dist-info/RECORD,,