runtimepy 5.7.1__py3-none-any.whl → 5.7.2__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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.1.4
4
- # hash=405585c0fbfcc966ef193f744f21fa59
4
+ # hash=0a0c607a8f78dc35cf48427539c9bafa
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.1"
13
+ VERSION = "5.7.2"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
runtimepy/data/js/main.js CHANGED
@@ -1,6 +1,14 @@
1
+ function changeFileName(urlObj, newFileName) {
2
+ let pathParts = urlObj.pathname.split('/');
3
+ pathParts[pathParts.length - 1] = newFileName;
4
+ return pathParts.join('/');
5
+ }
6
+
1
7
  /* Load configuration data then run application entry. */
2
8
  window.onload = async () => {
3
- await (new App(await (await fetch(window.location.origin + "/json")).json(),
4
- worker))
9
+ await (
10
+ new App(
11
+ await (await fetch(changeFileName(window.location, "json"))).json(),
12
+ worker))
5
13
  .main();
6
14
  };
@@ -3,11 +3,12 @@ A module implementing a server interface for this package.
3
3
  """
4
4
 
5
5
  # built-in
6
+ import http
6
7
  from io import StringIO
7
8
  import logging
8
9
  import mimetypes
9
10
  from pathlib import Path
10
- from typing import Any, Optional, TextIO
11
+ from typing import Any, Optional, TextIO, Union
11
12
 
12
13
  # third-party
13
14
  from vcorelib.io import JsonObject
@@ -22,6 +23,7 @@ from runtimepy.net.http.response import ResponseHeader
22
23
  from runtimepy.net.server.html import HtmlApp, HtmlApps, html_handler
23
24
  from runtimepy.net.server.json import encode_json, json_handler
24
25
  from runtimepy.net.tcp.http import HttpConnection
26
+ from runtimepy.util import normalize_root, path_has_part
25
27
 
26
28
  MIMETYPES_INIT = False
27
29
 
@@ -48,6 +50,7 @@ class RuntimepyServerConnection(HttpConnection):
48
50
 
49
51
  paths: list[Path]
50
52
  class_paths: list[Pathlike] = [Path(), package_data_dir()]
53
+ class_redirect_paths: dict[Path, Union[str, Path]] = {}
51
54
 
52
55
  def add_path(self, path: Pathlike, front: bool = False) -> None:
53
56
  """Add a path."""
@@ -60,6 +63,19 @@ class RuntimepyServerConnection(HttpConnection):
60
63
 
61
64
  self.log_paths()
62
65
 
66
+ @classmethod
67
+ def add_redirect_path(
68
+ cls, dest: Union[str, Path], *src_parts: Union[str, Path]
69
+ ) -> None:
70
+ """Add a redirect path."""
71
+
72
+ source = normalize_root(*src_parts)
73
+ assert source not in cls.class_redirect_paths, (
74
+ source,
75
+ cls.class_redirect_paths,
76
+ )
77
+ cls.class_redirect_paths[source] = dest
78
+
63
79
  def log_paths(self) -> None:
64
80
  """Log search paths."""
65
81
 
@@ -90,6 +106,23 @@ class RuntimepyServerConnection(HttpConnection):
90
106
  with favicon.open("rb") as favicon_fd:
91
107
  type(self).favicon_data = favicon_fd.read()
92
108
 
109
+ def try_redirect(
110
+ self, path: PathMaybeQuery, response: ResponseHeader
111
+ ) -> Optional[bytes]:
112
+ """Try handling any HTTP redirect rules."""
113
+
114
+ result = None
115
+
116
+ curr = Path(path[0])
117
+ if curr in self.class_redirect_paths:
118
+ response["Location"] = str(self.class_redirect_paths[curr])
119
+ response.status = http.HTTPStatus.TEMPORARY_REDIRECT
120
+
121
+ # No data payload, but signal to caller that a response is ready.
122
+ result = bytes()
123
+
124
+ return result
125
+
93
126
  def try_file(
94
127
  self, path: PathMaybeQuery, response: ResponseHeader
95
128
  ) -> Optional[bytes]:
@@ -188,13 +221,14 @@ class RuntimepyServerConnection(HttpConnection):
188
221
  response["Content-Type"] = "image/x-icon"
189
222
  return self.favicon_data
190
223
 
191
- # Try serving the path as a file.
192
- result = self.try_file(request.target.origin_form, response)
193
- if result is not None:
194
- return result
224
+ # Try serving a file and handling redirects.
225
+ for handler in [self.try_redirect, self.try_file]:
226
+ result = handler(request.target.origin_form, response)
227
+ if result is not None:
228
+ return result
195
229
 
196
230
  # Handle raw data queries.
197
- if path.startswith("/json"):
231
+ if path_has_part(request.target.path):
198
232
  json_handler(
199
233
  stream,
200
234
  request,
@@ -73,6 +73,11 @@ async def setup(app: AppInfo) -> int:
73
73
  RuntimepyServerConnection.apps["/app.html"] = web_app
74
74
  RuntimepyServerConnection.default_app = web_app
75
75
 
76
+ # Register redirects.
77
+ redirects: dict[str, str] = app.config_param("http_redirects", {})
78
+ for key, val in redirects.items():
79
+ RuntimepyServerConnection.add_redirect_path(val, key)
80
+
76
81
  # Register custom applications.
77
82
  for key, app_method in APPS.items():
78
83
  app_cfg: dict[str, Any] = app.config_param(key, {})
@@ -13,6 +13,7 @@ from vcorelib.io import ARBITER, JsonObject
13
13
  # internal
14
14
  from runtimepy.net.http.header import RequestHeader
15
15
  from runtimepy.net.http.response import ResponseHeader
16
+ from runtimepy.util import parse_path_parts
16
17
 
17
18
 
18
19
  class Encoder(JSONEncoder):
@@ -94,7 +95,7 @@ def json_handler(
94
95
  del request_data
95
96
 
96
97
  # Traverse path.
97
- data = traverse_dict(data, *request.target.path.split("/")[2:])
98
+ data = traverse_dict(data, *parse_path_parts(request.target.path))
98
99
 
99
100
  # Use a convention for indexing data to non-dictionary leaf nodes.
100
101
  if not isinstance(data, dict):
runtimepy/util.py CHANGED
@@ -2,6 +2,25 @@
2
2
  A module implementing package utilities.
3
3
  """
4
4
 
5
+ # built-in
6
+ from os import sep
7
+ from pathlib import Path
8
+ from typing import Iterator, Union
9
+
10
+ # third-party
11
+ from vcorelib.paths import normalize
12
+
13
+ ROOT_PATH = Path(sep)
14
+
15
+
16
+ def normalize_root(*src_parts: Union[str, Path]) -> Path:
17
+ """Make paths absolute that aren't. Useful for HTTP-request pathing."""
18
+
19
+ result = normalize(*src_parts)
20
+ if not result.is_absolute():
21
+ result = ROOT_PATH.joinpath(result)
22
+ return result
23
+
5
24
 
6
25
  class Identifier:
7
26
  """A simple message indentifier interface."""
@@ -16,3 +35,26 @@ class Identifier:
16
35
  curr = self.curr_id
17
36
  self.curr_id += self.scale
18
37
  return curr
38
+
39
+
40
+ def path_has_part(path: str, key: str = "json") -> bool:
41
+ """Determine if a key appears as a part of a path."""
42
+
43
+ # Ignore '/' component (intended for URI paths).
44
+ return key in path.split("/")[1:]
45
+
46
+
47
+ def parse_path_parts(path: str, key: str = "json") -> Iterator[str]:
48
+ """
49
+ Parse a path such that all parts appearing after a possible 'key' appears
50
+ are yielded.
51
+ """
52
+
53
+ key_found = False
54
+
55
+ # Ignore '/' component (intended for URI paths).
56
+ for item in path.split("/")[1:]:
57
+ if key_found:
58
+ yield item
59
+ elif item == key:
60
+ key_found = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: runtimepy
3
- Version: 5.7.1
3
+ Version: 5.7.2
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/vkottler/runtimepy
6
6
  Author: Vaughn Kottler
@@ -17,11 +17,11 @@ 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
20
21
  Requires-Dist: websockets
21
- Requires-Dist: svgen >=0.6.8
22
- Requires-Dist: psutil
23
22
  Requires-Dist: aiofiles
24
- Requires-Dist: vcorelib >=3.4.2
23
+ Requires-Dist: psutil
24
+ Requires-Dist: svgen >=0.6.8
25
25
  Provides-Extra: test
26
26
  Requires-Dist: pylint ; extra == 'test'
27
27
  Requires-Dist: flake8 ; 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=8247340c8c4e6982a54b831c311f2c0b
48
+ hash=618546df79dd7f387faa6f84f9a58c79
49
49
  =====================================
50
50
  -->
51
51
 
52
- # runtimepy ([5.7.1](https://pypi.org/project/runtimepy/))
52
+ # runtimepy ([5.7.2](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=5cxrac3CYhDfd63ssWuRNlnKUGeMyi01Ivyg3V_as0I,390
1
+ runtimepy/__init__.py,sha256=52lFAvkdOBUthOa7tJ66FInklDlbnSn3q9BoqWKadlY,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
@@ -7,7 +7,7 @@ runtimepy/mapping.py,sha256=VQK1vzmQVvYYKI85_II37-hIEbvgL3PzNy-WI6TTo80,5091
7
7
  runtimepy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  runtimepy/requirements.txt,sha256=77PnkPte9OZyyJg88wF_6su2H4XSvuN8rQH1tibLdv4,124
9
9
  runtimepy/schemas.py,sha256=NROlzVCZGLTJ9xe7d1eNIYmjzf0AOv3qHK-f6g1jGHk,678
10
- runtimepy/util.py,sha256=q1Spqt8AiG8_64COh1fU5cUKl-cZhp8BK1CHw_IOya0,399
10
+ runtimepy/util.py,sha256=1279Ea7CNHa1U_JvUydGAb2oEI2TxilXkq5V1RseCwU,1437
11
11
  runtimepy/channel/__init__.py,sha256=pf0RJ5g37_FVV8xoUNgzFGuIfbZEYSBA_cQlJSDTPDo,4774
12
12
  runtimepy/channel/registry.py,sha256=nk36qM_Bf6qK6AFR0plaZHR1PU7b4LZqbQ0feJqk4lc,4784
13
13
  runtimepy/channel/environment/__init__.py,sha256=0Jj8g7Y4bdDvmWtzpegB9D4milGPhsZokoYxmps5zgU,1612
@@ -58,7 +58,7 @@ runtimepy/data/js/JsonConnection.js,sha256=rclZrbmWc_zSs6I_JhOgxnVPFIyPMo5WdjAe8
58
58
  runtimepy/data/js/audio.js,sha256=bLkBqbeHMiGGidfL3iXjmVoF9seK-ZeZ3kwgOrcpgk4,1092
59
59
  runtimepy/data/js/events.js,sha256=rgz3Q_8J6sfU_7Sa7fG1mZD0pQ4S3vwN2mqcvQfePkM,554
60
60
  runtimepy/data/js/init.js,sha256=IeFqfab7CM2-Z4fIbyGaUD4M2orUT8uLwcVlleQqXzg,1522
61
- runtimepy/data/js/main.js,sha256=r0P_0xx5Czd1jfTjsB-tLfwhp4iPNoajlYC858u0ltc,211
61
+ runtimepy/data/js/main.js,sha256=nYIQ6O76EWqlzwX7oEwPXqC-LCUFCZYDADK9QbYRDKk,404
62
62
  runtimepy/data/js/util.js,sha256=Xc8pHUiFDBDvIqTamWrCYUOpF7iR9VNvPDCSCQAfLDA,1424
63
63
  runtimepy/data/js/worker.js,sha256=V9deGAynjvUr1D-WGi3wUW8rxoaNLvBvayMoLFZk3w0,2444
64
64
  runtimepy/data/js/classes/App.js,sha256=nnY42Q3tlNzf8JZtuGKyxJZLLNMfResdww8svOQMC3U,3402
@@ -171,10 +171,10 @@ runtimepy/net/http/request_target.py,sha256=EE1aI5VSARw1h93jyZvP56ir5O5fjd6orYK-
171
171
  runtimepy/net/http/response.py,sha256=Sup8W_A0ADNzR5olKrQsVNhsQXUwPOD-eJLlLOgYlAY,2316
172
172
  runtimepy/net/http/state.py,sha256=qCMN8aWfCRfU9XP-cIhSOo2RqfljTjbQRCflfcy2bfY,1626
173
173
  runtimepy/net/http/version.py,sha256=mp6rgIM7-VUVKLCA0Uw96CmBkL0ET860lDVVEewpZ7w,1098
174
- runtimepy/net/server/__init__.py,sha256=J8gl91YltD8Wo2y_AXxaL6liLu3vomfzUz_nULa3e2Y,6707
174
+ runtimepy/net/server/__init__.py,sha256=sxAOQc6jywBYqqZOCuq_jRJMVoA0ZFaFy2hDpgoOtSs,7845
175
175
  runtimepy/net/server/html.py,sha256=xaTGelH4zrwndQjU24kbCj9Yqu-D17nK5682P6xa-cU,1153
176
- runtimepy/net/server/json.py,sha256=RfNt7Gr4-X5DMinV1UeiWneTIJr0LO6BXo2GzE0C1PQ,2475
177
- runtimepy/net/server/app/__init__.py,sha256=9gJr7qcrJNQacAOFpZLfpnzDHzx5KXBMNxPlqQsdwB8,2790
176
+ runtimepy/net/server/json.py,sha256=a7vM5yfq2er4DexzFqEMnxoMGDeuywKkVH4-uNJBAik,2522
177
+ runtimepy/net/server/app/__init__.py,sha256=Q5NWlAkIZE2oEHkGdILFyQoX1Fw1lMR1lVQyMVkJ_Hc,2989
178
178
  runtimepy/net/server/app/base.py,sha256=9PnonII2qZIf1O9BQ8T-wOAgAVHctpLqyZkWGX2M2r4,2104
179
179
  runtimepy/net/server/app/create.py,sha256=N-g3kClBsG4pKOd9tx947rOq4sfgrH_FAMVfZacjhFA,2666
180
180
  runtimepy/net/server/app/elements.py,sha256=KJt9vWqkfvniJMiLOJN467JjPPrEqJYZXmDuY1JoY1g,455
@@ -280,9 +280,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
280
280
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
281
281
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
282
282
  runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
283
- runtimepy-5.7.1.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
284
- runtimepy-5.7.1.dist-info/METADATA,sha256=eqlwkUrGcPJFbn5jXvluP9ldQxHabTy2-tfbUNdiP8s,9308
285
- runtimepy-5.7.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
286
- runtimepy-5.7.1.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
287
- runtimepy-5.7.1.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
288
- runtimepy-5.7.1.dist-info/RECORD,,
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,,