h2o-wave 0.25.3__py3-none-any.whl → 0.26.1__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.
Potentially problematic release.
This version of h2o-wave might be problematic. Click here for more details.
- h2o_wave/cli.py +113 -24
- h2o_wave/core.py +35 -18
- h2o_wave/py.typed +0 -0
- h2o_wave/routing.py +1 -1
- h2o_wave/server.py +8 -2
- h2o_wave/share.py +26 -0
- h2o_wave/types.py +132 -5
- h2o_wave/ui.py +49 -4
- h2o_wave/version.py +1 -1
- {h2o_wave-0.25.3.data → h2o_wave-0.26.1.data}/data/project_templates/hello_world.py +1 -1
- {h2o_wave-0.25.3.dist-info → h2o_wave-0.26.1.dist-info}/METADATA +1 -1
- h2o_wave-0.26.1.dist-info/RECORD +29 -0
- h2o_wave-0.25.3.dist-info/RECORD +0 -27
- {h2o_wave-0.25.3.data → h2o_wave-0.26.1.data}/data/project_templates/README.md +0 -0
- {h2o_wave-0.25.3.data → h2o_wave-0.26.1.data}/data/project_templates/header.py +0 -0
- {h2o_wave-0.25.3.data → h2o_wave-0.26.1.data}/data/project_templates/header_nav.py +0 -0
- {h2o_wave-0.25.3.data → h2o_wave-0.26.1.data}/data/project_templates/header_sidebar_nav.py +0 -0
- {h2o_wave-0.25.3.data → h2o_wave-0.26.1.data}/data/project_templates/sidebar_nav.py +0 -0
- {h2o_wave-0.25.3.dist-info → h2o_wave-0.26.1.dist-info}/LICENSE +0 -0
- {h2o_wave-0.25.3.dist-info → h2o_wave-0.26.1.dist-info}/WHEEL +0 -0
- {h2o_wave-0.25.3.dist-info → h2o_wave-0.26.1.dist-info}/entry_points.txt +0 -0
- {h2o_wave-0.25.3.dist-info → h2o_wave-0.26.1.dist-info}/top_level.txt +0 -0
h2o_wave/cli.py
CHANGED
|
@@ -12,34 +12,43 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import
|
|
15
|
+
import asyncio
|
|
16
|
+
import os
|
|
17
|
+
import platform
|
|
17
18
|
import shutil
|
|
18
|
-
import sys
|
|
19
19
|
import socket
|
|
20
|
-
from contextlib import closing
|
|
21
20
|
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import tarfile
|
|
23
|
+
import time
|
|
24
|
+
from contextlib import closing
|
|
22
25
|
from pathlib import Path
|
|
23
|
-
import platform
|
|
24
|
-
import uvicorn
|
|
25
|
-
import click
|
|
26
|
-
import inquirer
|
|
27
|
-
import os
|
|
28
26
|
from urllib import request
|
|
29
27
|
from urllib.parse import urlparse
|
|
28
|
+
|
|
29
|
+
import click
|
|
30
|
+
import httpx
|
|
31
|
+
import inquirer
|
|
32
|
+
import uvicorn
|
|
33
|
+
from click import Choice, option
|
|
34
|
+
from h2o_wave.share import listen_on_socket
|
|
35
|
+
|
|
36
|
+
from .metadata import __arch__, __platform__
|
|
30
37
|
from .version import __version__
|
|
31
|
-
from .metadata import __platform__, __arch__
|
|
32
38
|
|
|
33
39
|
_localhost = '127.0.0.1'
|
|
34
40
|
|
|
41
|
+
|
|
35
42
|
def read_file(file: str) -> str:
|
|
36
43
|
with open(file, 'r') as f:
|
|
37
44
|
return f.read()
|
|
38
45
|
|
|
46
|
+
|
|
39
47
|
def write_file(file: str, content: str) -> None:
|
|
40
48
|
with open(file, 'w') as f:
|
|
41
49
|
f.write(content)
|
|
42
50
|
|
|
51
|
+
|
|
43
52
|
def _scan_free_port(port: int = 8000):
|
|
44
53
|
while True:
|
|
45
54
|
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
|
|
@@ -52,7 +61,7 @@ def is_within_directory(directory, target):
|
|
|
52
61
|
abs_directory = os.path.abspath(directory)
|
|
53
62
|
abs_target = os.path.abspath(target)
|
|
54
63
|
prefix = os.path.commonprefix([abs_directory, abs_target])
|
|
55
|
-
|
|
64
|
+
|
|
56
65
|
return prefix == abs_directory
|
|
57
66
|
|
|
58
67
|
|
|
@@ -62,8 +71,8 @@ def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
|
|
|
62
71
|
if not is_within_directory(path, member_path):
|
|
63
72
|
raise Exception("Attempted Path Traversal in Tar File")
|
|
64
73
|
|
|
65
|
-
tar.extractall(path, members, numeric_owner=numeric_owner)
|
|
66
|
-
|
|
74
|
+
tar.extractall(path, members, numeric_owner=numeric_owner)
|
|
75
|
+
|
|
67
76
|
|
|
68
77
|
@click.group()
|
|
69
78
|
def main():
|
|
@@ -115,15 +124,18 @@ def run(app: str, no_reload: bool, no_autostart: bool):
|
|
|
115
124
|
# Try to start Wave daemon if not running or turned off.
|
|
116
125
|
server_port = int(os.environ.get('H2O_WAVE_LISTEN', ':10101').split(':')[-1])
|
|
117
126
|
server_not_running = _scan_free_port(server_port) == server_port
|
|
127
|
+
|
|
128
|
+
waved_process = None
|
|
129
|
+
if no_autostart:
|
|
130
|
+
autostart = False
|
|
131
|
+
else:
|
|
132
|
+
autostart = os.environ.get('H2O_WAVE_NO_AUTOSTART', 'false').lower() in ['false', '0', 'f']
|
|
133
|
+
|
|
134
|
+
waved = 'waved.exe' if 'Windows' in platform.system() else './waved'
|
|
135
|
+
# OS agnostic wheels do not have waved - needed for HAC.
|
|
136
|
+
is_waved_present = os.path.isfile(os.path.join(sys.exec_prefix, waved))
|
|
137
|
+
|
|
118
138
|
try:
|
|
119
|
-
waved = 'waved.exe' if 'Windows' in platform.system() else './waved'
|
|
120
|
-
waved_process = None
|
|
121
|
-
# OS agnostic wheels do not have waved - needed for HAC.
|
|
122
|
-
is_waved_present = os.path.isfile(os.path.join(sys.exec_prefix, waved))
|
|
123
|
-
if no_autostart:
|
|
124
|
-
autostart = False
|
|
125
|
-
else:
|
|
126
|
-
autostart = os.environ.get('H2O_WAVE_NO_AUTOSTART', 'false').lower() in ['false', '0', 'f']
|
|
127
139
|
if autostart and is_waved_present and server_not_running:
|
|
128
140
|
waved_process = subprocess.Popen([waved], cwd=sys.exec_prefix, env=os.environ.copy(), shell=True)
|
|
129
141
|
time.sleep(1)
|
|
@@ -154,14 +166,23 @@ def ide():
|
|
|
154
166
|
|
|
155
167
|
|
|
156
168
|
@main.command()
|
|
157
|
-
|
|
169
|
+
@option('--platform', help='Operating system type.', type=Choice(['linux', 'windows', 'darwin']))
|
|
170
|
+
@option('--arch', default=__arch__, help='Processor architecture type.', type=Choice(['amd64', 'arm64']))
|
|
171
|
+
def fetch(platform: str, arch: str):
|
|
158
172
|
"""Download examples and related files to ./wave.
|
|
159
173
|
|
|
160
174
|
\b
|
|
161
175
|
$ wave fetch
|
|
162
176
|
"""
|
|
177
|
+
if not platform:
|
|
178
|
+
platform = __platform__
|
|
179
|
+
|
|
180
|
+
if platform == 'any':
|
|
181
|
+
print('Platform could not be detected. Please specify manually via --platform param.')
|
|
182
|
+
return
|
|
183
|
+
|
|
163
184
|
print('Fetching examples and related files. Please wait...')
|
|
164
|
-
tar_name = f'wave-{__version__}-{
|
|
185
|
+
tar_name = f'wave-{__version__}-{platform}-{arch}'
|
|
165
186
|
tar_file = f'{tar_name}.tar.gz'
|
|
166
187
|
tar_url = f'https://github.com/h2oai/wave/releases/download/v{__version__}/{tar_file}'
|
|
167
188
|
tar_path = Path(tar_file)
|
|
@@ -263,4 +284,72 @@ def learn():
|
|
|
263
284
|
from h2o_wave_university import cli
|
|
264
285
|
cli.main()
|
|
265
286
|
except ImportError:
|
|
266
|
-
print('You need to run \x1b[7;30;43mpip install h2o_wave_university\x1b[0m first.')
|
|
287
|
+
print('You need to run \x1b[7;30;43mpip install h2o_wave_university\x1b[0m first.')
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@main.command()
|
|
291
|
+
@click.option('--port', default=10101, help='Port your app is running on (defaults to 10101).')
|
|
292
|
+
@click.option('--subdomain', default='?new', help='Subdomain to use. If not available, a random one is generated.')
|
|
293
|
+
@click.option('--remote-host', default='h2oai.app', help='Remote host to use (defaults to h2oai.app).')
|
|
294
|
+
def share(port: int, subdomain: str, remote_host: str):
|
|
295
|
+
"""Share your locally running app with the world.
|
|
296
|
+
|
|
297
|
+
\b
|
|
298
|
+
$ wave share
|
|
299
|
+
"""
|
|
300
|
+
if 'Windows' in platform.system():
|
|
301
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
302
|
+
|
|
303
|
+
loop = asyncio.new_event_loop()
|
|
304
|
+
asyncio.set_event_loop(loop)
|
|
305
|
+
|
|
306
|
+
if 'Windows' in platform.system():
|
|
307
|
+
|
|
308
|
+
async def wakeup():
|
|
309
|
+
while True:
|
|
310
|
+
await asyncio.sleep(1)
|
|
311
|
+
|
|
312
|
+
# HACK: Enable Ctrl-C on Windows when opening multiple TCP connections.
|
|
313
|
+
# https://stackoverflow.com/questions/27480967/why-does-the-asyncios-event-loop-suppress-the-keyboardinterrupt-on-windows.
|
|
314
|
+
loop.create_task(wakeup())
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
loop.run_until_complete(_share(port, subdomain, remote_host))
|
|
318
|
+
except KeyboardInterrupt:
|
|
319
|
+
tasks = asyncio.all_tasks(loop)
|
|
320
|
+
for task in tasks:
|
|
321
|
+
task.cancel()
|
|
322
|
+
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
|
|
323
|
+
loop.close()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
async def _share(port: int, subdomain: str, remote_host: str):
|
|
327
|
+
if _scan_free_port(port) == port:
|
|
328
|
+
print(f'Could not connect to localhost:{port}. Please make sure your app is running.')
|
|
329
|
+
exit(1)
|
|
330
|
+
|
|
331
|
+
res = httpx.get(f'https://{remote_host}/{subdomain}', headers={'Content-Type': 'application/json'})
|
|
332
|
+
if res.status_code != 200:
|
|
333
|
+
print('Could not connect to the remote sharing server.')
|
|
334
|
+
exit(1)
|
|
335
|
+
|
|
336
|
+
res = res.json()
|
|
337
|
+
print(f'BETA: Proxying localhost:{port} ==> {res["url"]}')
|
|
338
|
+
print('\x1b[7;30;43mDO NOT SHARE YOUR APP IF IT CONTAINS SENSITIVE INFO\x1b[0m.')
|
|
339
|
+
print('Press Ctrl+C to stop sharing.')
|
|
340
|
+
|
|
341
|
+
max_conn_count = res['max_conn_count']
|
|
342
|
+
# The server can be configured to either support 10 concurrent connections (default) or more.
|
|
343
|
+
# If more, connect in batches of 100 for better performance.
|
|
344
|
+
step = 100 if max_conn_count > 10 else max_conn_count
|
|
345
|
+
|
|
346
|
+
tasks = []
|
|
347
|
+
for _ in range(max_conn_count // step):
|
|
348
|
+
for _ in range(step):
|
|
349
|
+
tasks.append(asyncio.create_task(listen_on_socket('127.0.0.1', port, remote_host, res['port'])))
|
|
350
|
+
await asyncio.sleep(1)
|
|
351
|
+
# Handle the rest if any.
|
|
352
|
+
for _ in range(max_conn_count % step):
|
|
353
|
+
tasks.append(asyncio.create_task(listen_on_socket('127.0.0.1', port, remote_host, res['port'])))
|
|
354
|
+
|
|
355
|
+
await asyncio.gather(*tasks)
|
h2o_wave/core.py
CHANGED
|
@@ -299,6 +299,13 @@ class Ref:
|
|
|
299
299
|
raise ValueError('Data instances cannot be used in assignments.')
|
|
300
300
|
getattr(self, PAGE)._track(_set_op(self, key, _dump(value)))
|
|
301
301
|
|
|
302
|
+
def __iadd__(self, value):
|
|
303
|
+
if not getattr(self, KEY).endswith('data'):
|
|
304
|
+
raise ValueError('+= can only be used on list data buffers.')
|
|
305
|
+
page = getattr(self, PAGE)
|
|
306
|
+
page._track(_set_op(self, '__append__', _dump(value)))
|
|
307
|
+
page._skip_next_track = True
|
|
308
|
+
|
|
302
309
|
|
|
303
310
|
class Data:
|
|
304
311
|
"""
|
|
@@ -306,35 +313,38 @@ class Data:
|
|
|
306
313
|
|
|
307
314
|
Args:
|
|
308
315
|
fields: The names of the fields (columns names) in the data, either a list or tuple or string containing space-separated names.
|
|
309
|
-
size: The number of rows to allocate memory for. Positive for fixed buffers, negative for
|
|
310
|
-
data: Initial data. Must be either a key-row ``dict`` for variable-length buffers OR a row ``list`` for fixed-size and
|
|
316
|
+
size: The number of rows to allocate memory for. Positive for fixed buffers, negative for cyclic buffers and zero for variable length buffers.
|
|
317
|
+
data: Initial data. Must be either a key-row ``dict`` for variable-length buffers OR a row ``list`` for fixed-size and cyclic buffers.
|
|
318
|
+
t: Buffer type. One of 'list', 'map', 'cyclic' or 'fixed'. Overrides the buffer type inferred from the size.
|
|
311
319
|
"""
|
|
312
320
|
|
|
313
|
-
def __init__(self, fields: Union[str, tuple, list], size: int = 0, data: Optional[Union[dict, list]] = None):
|
|
321
|
+
def __init__(self, fields: Union[str, tuple, list], size: int = 0, data: Optional[Union[dict, list]] = None, t: Optional[str] = None):
|
|
314
322
|
self.fields = fields
|
|
315
323
|
self.data = data
|
|
316
324
|
self.size = size
|
|
325
|
+
self.type = t
|
|
317
326
|
|
|
318
327
|
def dump(self):
|
|
319
328
|
f = self.fields
|
|
320
329
|
d = self.data
|
|
321
330
|
n = self.size
|
|
331
|
+
t = self.type
|
|
322
332
|
if d:
|
|
323
|
-
if
|
|
333
|
+
if t == 'list':
|
|
334
|
+
return dict(l=dict(f=f, d=d))
|
|
335
|
+
if t == 'map' or isinstance(d, dict):
|
|
324
336
|
return dict(m=dict(f=f, d=d))
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
else:
|
|
329
|
-
return dict(f=dict(f=f, d=d))
|
|
337
|
+
if t == 'cyclic' or n < 0:
|
|
338
|
+
return dict(c=dict(f=f, d=d))
|
|
339
|
+
return dict(f=dict(f=f, d=d))
|
|
330
340
|
else:
|
|
331
|
-
if
|
|
341
|
+
if t == 'list':
|
|
342
|
+
return dict(l=dict(f=f, n=n))
|
|
343
|
+
if t == 'map' or n == 0:
|
|
332
344
|
return dict(m=dict(f=f))
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
else:
|
|
337
|
-
return dict(f=dict(f=f, n=n))
|
|
345
|
+
if t == 'cyclic' or n < 0:
|
|
346
|
+
return dict(c=dict(f=f, n=-n))
|
|
347
|
+
return dict(f=dict(f=f, n=n))
|
|
338
348
|
|
|
339
349
|
|
|
340
350
|
def data(
|
|
@@ -343,6 +353,7 @@ def data(
|
|
|
343
353
|
rows: Optional[Union[dict, list]] = None,
|
|
344
354
|
columns: Optional[Union[dict, list]] = None,
|
|
345
355
|
pack=False,
|
|
356
|
+
t: Optional[str] = None,
|
|
346
357
|
) -> Union[Data, str]:
|
|
347
358
|
"""
|
|
348
359
|
Create a `h2o_wave.core.Data` instance for associating data with cards.
|
|
@@ -355,10 +366,11 @@ def data(
|
|
|
355
366
|
|
|
356
367
|
Args:
|
|
357
368
|
fields: The names of the fields (columns names) in the data, either a list or tuple or string containing space-separated names.
|
|
358
|
-
size: The number of rows to allocate memory for. Positive for fixed buffers, negative for
|
|
369
|
+
size: The number of rows to allocate memory for. Positive for fixed buffers, negative for cyclic buffers and zero for variable length buffers.
|
|
359
370
|
rows: The rows in this data.
|
|
360
371
|
columns: The columns in this data.
|
|
361
372
|
pack: True to return a packed string representing the data instead of a `h2o_wave.core.Data` placeholder.
|
|
373
|
+
t: Buffer type. One of 'list', 'map', 'cyclic' or 'fixed'. Overrides the buffer type inferred from the size.
|
|
362
374
|
|
|
363
375
|
Returns:
|
|
364
376
|
Either a `h2o_wave.core.Data` placeholder or a packed string representing the data.
|
|
@@ -404,7 +416,7 @@ def data(
|
|
|
404
416
|
if not _is_int(size):
|
|
405
417
|
raise ValueError('size must be int')
|
|
406
418
|
|
|
407
|
-
return Data(fields, size, rows)
|
|
419
|
+
return Data(fields, size, rows, t)
|
|
408
420
|
|
|
409
421
|
|
|
410
422
|
class _ServerCacheBase:
|
|
@@ -468,6 +480,8 @@ class PageBase:
|
|
|
468
480
|
def __init__(self, url: str):
|
|
469
481
|
self.url = url
|
|
470
482
|
self._changes = []
|
|
483
|
+
# HACK: Overloading += operator makes unnecessary __setattr__ call. Skip it to prevent redundant ops.
|
|
484
|
+
self._skip_next_track = False
|
|
471
485
|
|
|
472
486
|
def add(self, key: str, card: Any) -> Ref:
|
|
473
487
|
"""
|
|
@@ -510,6 +524,9 @@ class PageBase:
|
|
|
510
524
|
return Ref(self, key)
|
|
511
525
|
|
|
512
526
|
def _track(self, op: dict):
|
|
527
|
+
if self._skip_next_track:
|
|
528
|
+
self._skip_next_track = False
|
|
529
|
+
return
|
|
513
530
|
self._changes.append(op)
|
|
514
531
|
|
|
515
532
|
def _diff(self):
|
|
@@ -1151,4 +1168,4 @@ def _can_do_local_upload(data_dir: str, waved_dir: str) -> bool:
|
|
|
1151
1168
|
if not _is_loopback_address():
|
|
1152
1169
|
return False
|
|
1153
1170
|
|
|
1154
|
-
return os.path.isabs(data_dir) or (waved_dir and data_dir)
|
|
1171
|
+
return bool(os.path.isabs(data_dir) or (waved_dir and data_dir))
|
h2o_wave/py.typed
ADDED
|
File without changes
|
h2o_wave/routing.py
CHANGED
|
@@ -62,7 +62,7 @@ def on(arg: str = None, predicate: Optional[Callable] = None):
|
|
|
62
62
|
A function annotated with `@on('foo.bar', lambda x: 42 <= x <= 420)` is invoked whenever `q.events.foo.bar` between 42 and 420.
|
|
63
63
|
A function annotated with `@on('#foo')` is invoked whenever `q.args['#']` equals 'foo'.
|
|
64
64
|
A function annotated with `@on('#foo/bar')` is invoked whenever `q.args['#']` equals 'foo/bar'.
|
|
65
|
-
A function annotated with `@on('#foo
|
|
65
|
+
A function annotated with `@on('#foo/{"{fruit}"}')` is invoked whenever `q.args['#']` matches 'foo/apple', 'foo/orange', etc. The parameter 'fruit' is passed to the function (in this case, 'apple', 'orange', etc.)
|
|
66
66
|
|
|
67
67
|
Parameters in patterns (indicated within curly braces) can be converted to `str`, `int`, `float` or `uuid.UUID` instances by suffixing the parameters with `str`, `int`, `float` or `uuid`, respectively.
|
|
68
68
|
|
h2o_wave/server.py
CHANGED
|
@@ -80,6 +80,9 @@ class Auth:
|
|
|
80
80
|
"""
|
|
81
81
|
Explicitly refresh OIDC tokens when needed, e.g. during long-running background jobs.
|
|
82
82
|
"""
|
|
83
|
+
if not self.refresh_token:
|
|
84
|
+
return
|
|
85
|
+
|
|
83
86
|
async with httpx.AsyncClient(auth=(_config.hub_access_key_id, _config.hub_access_key_secret), verify=False) as http:
|
|
84
87
|
res = await http.get(_config.hub_address + '_auth/refresh', headers={'Wave-Session-ID': self._session_id})
|
|
85
88
|
return self.__extract_tokens(res.headers)
|
|
@@ -89,10 +92,12 @@ class Auth:
|
|
|
89
92
|
Explicitly refresh OIDC tokens when needed, e.g. during long-running background jobs - synchronous version.
|
|
90
93
|
Prefer async version. Use sync only when absolutely necessary - will block your app, making it slow for all users.
|
|
91
94
|
"""
|
|
95
|
+
if not self.refresh_token:
|
|
96
|
+
return
|
|
97
|
+
|
|
92
98
|
with httpx.Client(auth=(_config.hub_access_key_id, _config.hub_access_key_secret), verify=False) as http:
|
|
93
99
|
res = http.get(_config.hub_address + '_auth/refresh', headers={'Wave-Session-ID': self._session_id})
|
|
94
100
|
return self.__extract_tokens(res.headers)
|
|
95
|
-
|
|
96
101
|
|
|
97
102
|
def __extract_tokens(self, headers: httpx.Headers):
|
|
98
103
|
access_token = headers.get('Wave-Access-Token', None)
|
|
@@ -104,6 +109,7 @@ class Auth:
|
|
|
104
109
|
|
|
105
110
|
return access_token
|
|
106
111
|
|
|
112
|
+
|
|
107
113
|
class Query:
|
|
108
114
|
"""
|
|
109
115
|
Represents the query context.
|
|
@@ -373,7 +379,7 @@ class _App:
|
|
|
373
379
|
q.page.drop()
|
|
374
380
|
# TODO replace this with a custom-designed error display
|
|
375
381
|
q.page['__unhandled_error__'] = markdown_card(
|
|
376
|
-
box='1 1
|
|
382
|
+
box='1 1 -1 -1',
|
|
377
383
|
title='Error',
|
|
378
384
|
content=f'```\n{traceback.format_exc()}\n```',
|
|
379
385
|
)
|
h2o_wave/share.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def pipe(r: asyncio.StreamReader, w: asyncio.StreamWriter) -> None:
|
|
5
|
+
while True:
|
|
6
|
+
data = await r.read(4096)
|
|
7
|
+
if not data:
|
|
8
|
+
break
|
|
9
|
+
w.write(data)
|
|
10
|
+
await w.drain()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def listen_on_socket(local_host: str, local_port: int, remote_host: str, remote_port: int) -> None:
|
|
14
|
+
while True:
|
|
15
|
+
try:
|
|
16
|
+
local_reader, local_writer = await asyncio.open_connection(local_host, local_port)
|
|
17
|
+
remote_reader, remote_writer = await asyncio.open_connection(remote_host, remote_port, ssl=True)
|
|
18
|
+
|
|
19
|
+
await asyncio.gather(pipe(local_reader, remote_writer), pipe(remote_reader, local_writer))
|
|
20
|
+
|
|
21
|
+
# Swallow exceptions and reconnect.
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
finally:
|
|
25
|
+
local_writer.close()
|
|
26
|
+
remote_writer.close()
|
h2o_wave/types.py
CHANGED
|
@@ -20,7 +20,7 @@ from typing import Any, Optional, Union, Dict, List
|
|
|
20
20
|
from .core import Data
|
|
21
21
|
|
|
22
22
|
Value = Union[str, float, int]
|
|
23
|
-
PackedRecord = Union[dict, str]
|
|
23
|
+
PackedRecord = Union[dict, str, Data]
|
|
24
24
|
PackedRecords = Union[List[dict], str]
|
|
25
25
|
PackedData = Union[Data, str]
|
|
26
26
|
|
|
@@ -2603,6 +2603,7 @@ class Button:
|
|
|
2603
2603
|
visible: Optional[bool] = None,
|
|
2604
2604
|
tooltip: Optional[str] = None,
|
|
2605
2605
|
path: Optional[str] = None,
|
|
2606
|
+
commands: Optional[List[Command]] = None,
|
|
2606
2607
|
):
|
|
2607
2608
|
_guard_scalar('Button.name', name, (str,), True, False, False)
|
|
2608
2609
|
_guard_scalar('Button.label', label, (str,), False, True, False)
|
|
@@ -2616,6 +2617,7 @@ class Button:
|
|
|
2616
2617
|
_guard_scalar('Button.visible', visible, (bool,), False, True, False)
|
|
2617
2618
|
_guard_scalar('Button.tooltip', tooltip, (str,), False, True, False)
|
|
2618
2619
|
_guard_scalar('Button.path', path, (str,), False, True, False)
|
|
2620
|
+
_guard_vector('Button.commands', commands, (Command,), False, True, False)
|
|
2619
2621
|
self.name = name
|
|
2620
2622
|
"""An identifying name for this component. If the name is prefixed with a '#', the button sets the location hash to the name when clicked."""
|
|
2621
2623
|
self.label = label
|
|
@@ -2640,6 +2642,8 @@ class Button:
|
|
|
2640
2642
|
"""An optional tooltip message displayed when a user clicks the help icon to the right of the component."""
|
|
2641
2643
|
self.path = path
|
|
2642
2644
|
"""The path or URL to link to. If specified, the `name` is ignored. The URL is opened in a new browser window or tab."""
|
|
2645
|
+
self.commands = commands
|
|
2646
|
+
"""When specified, a split button is rendered with extra actions tied to it within a context menu. Mutually exclusive with `link` attribute."""
|
|
2643
2647
|
|
|
2644
2648
|
def dump(self) -> Dict:
|
|
2645
2649
|
"""Returns the contents of this object as a dict."""
|
|
@@ -2655,6 +2659,7 @@ class Button:
|
|
|
2655
2659
|
_guard_scalar('Button.visible', self.visible, (bool,), False, True, False)
|
|
2656
2660
|
_guard_scalar('Button.tooltip', self.tooltip, (str,), False, True, False)
|
|
2657
2661
|
_guard_scalar('Button.path', self.path, (str,), False, True, False)
|
|
2662
|
+
_guard_vector('Button.commands', self.commands, (Command,), False, True, False)
|
|
2658
2663
|
return _dump(
|
|
2659
2664
|
name=self.name,
|
|
2660
2665
|
label=self.label,
|
|
@@ -2668,6 +2673,7 @@ class Button:
|
|
|
2668
2673
|
visible=self.visible,
|
|
2669
2674
|
tooltip=self.tooltip,
|
|
2670
2675
|
path=self.path,
|
|
2676
|
+
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
|
|
2671
2677
|
)
|
|
2672
2678
|
|
|
2673
2679
|
@staticmethod
|
|
@@ -2697,6 +2703,8 @@ class Button:
|
|
|
2697
2703
|
_guard_scalar('Button.tooltip', __d_tooltip, (str,), False, True, False)
|
|
2698
2704
|
__d_path: Any = __d.get('path')
|
|
2699
2705
|
_guard_scalar('Button.path', __d_path, (str,), False, True, False)
|
|
2706
|
+
__d_commands: Any = __d.get('commands')
|
|
2707
|
+
_guard_vector('Button.commands', __d_commands, (dict,), False, True, False)
|
|
2700
2708
|
name: str = __d_name
|
|
2701
2709
|
label: Optional[str] = __d_label
|
|
2702
2710
|
caption: Optional[str] = __d_caption
|
|
@@ -2709,6 +2717,7 @@ class Button:
|
|
|
2709
2717
|
visible: Optional[bool] = __d_visible
|
|
2710
2718
|
tooltip: Optional[str] = __d_tooltip
|
|
2711
2719
|
path: Optional[str] = __d_path
|
|
2720
|
+
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
|
|
2712
2721
|
return Button(
|
|
2713
2722
|
name,
|
|
2714
2723
|
label,
|
|
@@ -2722,6 +2731,7 @@ class Button:
|
|
|
2722
2731
|
visible,
|
|
2723
2732
|
tooltip,
|
|
2724
2733
|
path,
|
|
2734
|
+
commands,
|
|
2725
2735
|
)
|
|
2726
2736
|
|
|
2727
2737
|
|
|
@@ -3440,7 +3450,7 @@ class TableColumn:
|
|
|
3440
3450
|
self.cell_overflow = cell_overflow
|
|
3441
3451
|
"""Defines what to do with a cell's contents in case it does not fit inside the cell. One of 'tooltip', 'wrap'. See enum h2o_wave.ui.TableColumnCellOverflow."""
|
|
3442
3452
|
self.filters = filters
|
|
3443
|
-
"""
|
|
3453
|
+
"""Explicit list of values to allow filtering by, needed when pagination is set or custom order is needed. Only applicable to filterable columns."""
|
|
3444
3454
|
self.align = align
|
|
3445
3455
|
"""Defines how to align values in a column. One of 'left', 'center', 'right'. See enum h2o_wave.ui.TableColumnAlign."""
|
|
3446
3456
|
|
|
@@ -3747,7 +3757,7 @@ class Table:
|
|
|
3747
3757
|
self.resettable = resettable
|
|
3748
3758
|
"""Indicates whether a Reset button should be displayed to reset search / filter / group-by values to their defaults. Defaults to False."""
|
|
3749
3759
|
self.height = height
|
|
3750
|
-
"""The height of the table
|
|
3760
|
+
"""The height of the table in px (e.g. '200px') or '1' to fill the remaining card space."""
|
|
3751
3761
|
self.width = width
|
|
3752
3762
|
"""The width of the table, e.g. '100px'. Defaults to '100%'."""
|
|
3753
3763
|
self.values = values
|
|
@@ -3763,7 +3773,7 @@ class Table:
|
|
|
3763
3773
|
self.pagination = pagination
|
|
3764
3774
|
"""Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`."""
|
|
3765
3775
|
self.events = events
|
|
3766
|
-
"""The events to capture on this table. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'."""
|
|
3776
|
+
"""The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'."""
|
|
3767
3777
|
self.single = single
|
|
3768
3778
|
"""True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr."""
|
|
3769
3779
|
self.value = value
|
|
@@ -6698,6 +6708,7 @@ class ImageAnnotator:
|
|
|
6698
6708
|
trigger: Optional[bool] = None,
|
|
6699
6709
|
image_height: Optional[str] = None,
|
|
6700
6710
|
allowed_shapes: Optional[List[str]] = None,
|
|
6711
|
+
events: Optional[List[str]] = None,
|
|
6701
6712
|
):
|
|
6702
6713
|
_guard_scalar('ImageAnnotator.name', name, (str,), True, False, False)
|
|
6703
6714
|
_guard_scalar('ImageAnnotator.image', image, (str,), False, False, False)
|
|
@@ -6707,6 +6718,7 @@ class ImageAnnotator:
|
|
|
6707
6718
|
_guard_scalar('ImageAnnotator.trigger', trigger, (bool,), False, True, False)
|
|
6708
6719
|
_guard_scalar('ImageAnnotator.image_height', image_height, (str,), False, True, False)
|
|
6709
6720
|
_guard_vector('ImageAnnotator.allowed_shapes', allowed_shapes, (str,), False, True, False)
|
|
6721
|
+
_guard_vector('ImageAnnotator.events', events, (str,), False, True, False)
|
|
6710
6722
|
self.name = name
|
|
6711
6723
|
"""An identifying name for this component."""
|
|
6712
6724
|
self.image = image
|
|
@@ -6723,6 +6735,8 @@ class ImageAnnotator:
|
|
|
6723
6735
|
"""The card’s image height. The actual image size is used by default."""
|
|
6724
6736
|
self.allowed_shapes = allowed_shapes
|
|
6725
6737
|
"""List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default."""
|
|
6738
|
+
self.events = events
|
|
6739
|
+
"""The events to capture on this image annotator. One of `click` or `tool_change`."""
|
|
6726
6740
|
|
|
6727
6741
|
def dump(self) -> Dict:
|
|
6728
6742
|
"""Returns the contents of this object as a dict."""
|
|
@@ -6734,6 +6748,7 @@ class ImageAnnotator:
|
|
|
6734
6748
|
_guard_scalar('ImageAnnotator.trigger', self.trigger, (bool,), False, True, False)
|
|
6735
6749
|
_guard_scalar('ImageAnnotator.image_height', self.image_height, (str,), False, True, False)
|
|
6736
6750
|
_guard_vector('ImageAnnotator.allowed_shapes', self.allowed_shapes, (str,), False, True, False)
|
|
6751
|
+
_guard_vector('ImageAnnotator.events', self.events, (str,), False, True, False)
|
|
6737
6752
|
return _dump(
|
|
6738
6753
|
name=self.name,
|
|
6739
6754
|
image=self.image,
|
|
@@ -6743,6 +6758,7 @@ class ImageAnnotator:
|
|
|
6743
6758
|
trigger=self.trigger,
|
|
6744
6759
|
image_height=self.image_height,
|
|
6745
6760
|
allowed_shapes=self.allowed_shapes,
|
|
6761
|
+
events=self.events,
|
|
6746
6762
|
)
|
|
6747
6763
|
|
|
6748
6764
|
@staticmethod
|
|
@@ -6764,6 +6780,8 @@ class ImageAnnotator:
|
|
|
6764
6780
|
_guard_scalar('ImageAnnotator.image_height', __d_image_height, (str,), False, True, False)
|
|
6765
6781
|
__d_allowed_shapes: Any = __d.get('allowed_shapes')
|
|
6766
6782
|
_guard_vector('ImageAnnotator.allowed_shapes', __d_allowed_shapes, (str,), False, True, False)
|
|
6783
|
+
__d_events: Any = __d.get('events')
|
|
6784
|
+
_guard_vector('ImageAnnotator.events', __d_events, (str,), False, True, False)
|
|
6767
6785
|
name: str = __d_name
|
|
6768
6786
|
image: str = __d_image
|
|
6769
6787
|
title: str = __d_title
|
|
@@ -6772,6 +6790,7 @@ class ImageAnnotator:
|
|
|
6772
6790
|
trigger: Optional[bool] = __d_trigger
|
|
6773
6791
|
image_height: Optional[str] = __d_image_height
|
|
6774
6792
|
allowed_shapes: Optional[List[str]] = __d_allowed_shapes
|
|
6793
|
+
events: Optional[List[str]] = __d_events
|
|
6775
6794
|
return ImageAnnotator(
|
|
6776
6795
|
name,
|
|
6777
6796
|
image,
|
|
@@ -6781,6 +6800,7 @@ class ImageAnnotator:
|
|
|
6781
6800
|
trigger,
|
|
6782
6801
|
image_height,
|
|
6783
6802
|
allowed_shapes,
|
|
6803
|
+
events,
|
|
6784
6804
|
)
|
|
6785
6805
|
|
|
6786
6806
|
|
|
@@ -6870,7 +6890,7 @@ class CopyableText:
|
|
|
6870
6890
|
self.multiline = multiline
|
|
6871
6891
|
"""True if the component should allow multi-line text entry."""
|
|
6872
6892
|
self.height = height
|
|
6873
|
-
"""Custom height in px
|
|
6893
|
+
"""Custom height in px (e.g. '200px') or '1' to fill the remaining card space. Requires `multiline` to be set."""
|
|
6874
6894
|
|
|
6875
6895
|
def dump(self) -> Dict:
|
|
6876
6896
|
"""Returns the contents of this object as a dict."""
|
|
@@ -7997,6 +8017,93 @@ class ChatCard:
|
|
|
7997
8017
|
)
|
|
7998
8018
|
|
|
7999
8019
|
|
|
8020
|
+
class ChatbotCard:
|
|
8021
|
+
"""Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers.
|
|
8022
|
+
"""
|
|
8023
|
+
def __init__(
|
|
8024
|
+
self,
|
|
8025
|
+
box: str,
|
|
8026
|
+
name: str,
|
|
8027
|
+
data: PackedRecord,
|
|
8028
|
+
placeholder: Optional[str] = None,
|
|
8029
|
+
events: Optional[List[str]] = None,
|
|
8030
|
+
generating: Optional[bool] = None,
|
|
8031
|
+
commands: Optional[List[Command]] = None,
|
|
8032
|
+
):
|
|
8033
|
+
_guard_scalar('ChatbotCard.box', box, (str,), False, False, False)
|
|
8034
|
+
_guard_scalar('ChatbotCard.name', name, (str,), True, False, False)
|
|
8035
|
+
_guard_scalar('ChatbotCard.placeholder', placeholder, (str,), False, True, False)
|
|
8036
|
+
_guard_vector('ChatbotCard.events', events, (str,), False, True, False)
|
|
8037
|
+
_guard_scalar('ChatbotCard.generating', generating, (bool,), False, True, False)
|
|
8038
|
+
_guard_vector('ChatbotCard.commands', commands, (Command,), False, True, False)
|
|
8039
|
+
self.box = box
|
|
8040
|
+
"""A string indicating how to place this component on the page."""
|
|
8041
|
+
self.name = name
|
|
8042
|
+
"""An identifying name for this component."""
|
|
8043
|
+
self.data = data
|
|
8044
|
+
"""Chat messages data. Requires cyclic buffer."""
|
|
8045
|
+
self.placeholder = placeholder
|
|
8046
|
+
"""Chat input box placeholder. Use for prompt examples."""
|
|
8047
|
+
self.events = events
|
|
8048
|
+
"""The events to capture on this chatbot. One of 'stop'."""
|
|
8049
|
+
self.generating = generating
|
|
8050
|
+
"""True to show a button to stop the text generation. Defaults to False."""
|
|
8051
|
+
self.commands = commands
|
|
8052
|
+
"""Contextual menu commands for this component."""
|
|
8053
|
+
|
|
8054
|
+
def dump(self) -> Dict:
|
|
8055
|
+
"""Returns the contents of this object as a dict."""
|
|
8056
|
+
_guard_scalar('ChatbotCard.box', self.box, (str,), False, False, False)
|
|
8057
|
+
_guard_scalar('ChatbotCard.name', self.name, (str,), True, False, False)
|
|
8058
|
+
_guard_scalar('ChatbotCard.placeholder', self.placeholder, (str,), False, True, False)
|
|
8059
|
+
_guard_vector('ChatbotCard.events', self.events, (str,), False, True, False)
|
|
8060
|
+
_guard_scalar('ChatbotCard.generating', self.generating, (bool,), False, True, False)
|
|
8061
|
+
_guard_vector('ChatbotCard.commands', self.commands, (Command,), False, True, False)
|
|
8062
|
+
return _dump(
|
|
8063
|
+
view='chatbot',
|
|
8064
|
+
box=self.box,
|
|
8065
|
+
name=self.name,
|
|
8066
|
+
data=self.data,
|
|
8067
|
+
placeholder=self.placeholder,
|
|
8068
|
+
events=self.events,
|
|
8069
|
+
generating=self.generating,
|
|
8070
|
+
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
|
|
8071
|
+
)
|
|
8072
|
+
|
|
8073
|
+
@staticmethod
|
|
8074
|
+
def load(__d: Dict) -> 'ChatbotCard':
|
|
8075
|
+
"""Creates an instance of this class using the contents of a dict."""
|
|
8076
|
+
__d_box: Any = __d.get('box')
|
|
8077
|
+
_guard_scalar('ChatbotCard.box', __d_box, (str,), False, False, False)
|
|
8078
|
+
__d_name: Any = __d.get('name')
|
|
8079
|
+
_guard_scalar('ChatbotCard.name', __d_name, (str,), True, False, False)
|
|
8080
|
+
__d_data: Any = __d.get('data')
|
|
8081
|
+
__d_placeholder: Any = __d.get('placeholder')
|
|
8082
|
+
_guard_scalar('ChatbotCard.placeholder', __d_placeholder, (str,), False, True, False)
|
|
8083
|
+
__d_events: Any = __d.get('events')
|
|
8084
|
+
_guard_vector('ChatbotCard.events', __d_events, (str,), False, True, False)
|
|
8085
|
+
__d_generating: Any = __d.get('generating')
|
|
8086
|
+
_guard_scalar('ChatbotCard.generating', __d_generating, (bool,), False, True, False)
|
|
8087
|
+
__d_commands: Any = __d.get('commands')
|
|
8088
|
+
_guard_vector('ChatbotCard.commands', __d_commands, (dict,), False, True, False)
|
|
8089
|
+
box: str = __d_box
|
|
8090
|
+
name: str = __d_name
|
|
8091
|
+
data: PackedRecord = __d_data
|
|
8092
|
+
placeholder: Optional[str] = __d_placeholder
|
|
8093
|
+
events: Optional[List[str]] = __d_events
|
|
8094
|
+
generating: Optional[bool] = __d_generating
|
|
8095
|
+
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
|
|
8096
|
+
return ChatbotCard(
|
|
8097
|
+
box,
|
|
8098
|
+
name,
|
|
8099
|
+
data,
|
|
8100
|
+
placeholder,
|
|
8101
|
+
events,
|
|
8102
|
+
generating,
|
|
8103
|
+
commands,
|
|
8104
|
+
)
|
|
8105
|
+
|
|
8106
|
+
|
|
8000
8107
|
_EditorCardMode = ['public', 'private']
|
|
8001
8108
|
|
|
8002
8109
|
|
|
@@ -8567,12 +8674,14 @@ class NavItem:
|
|
|
8567
8674
|
icon: Optional[str] = None,
|
|
8568
8675
|
disabled: Optional[bool] = None,
|
|
8569
8676
|
tooltip: Optional[str] = None,
|
|
8677
|
+
path: Optional[str] = None,
|
|
8570
8678
|
):
|
|
8571
8679
|
_guard_scalar('NavItem.name', name, (str,), True, False, False)
|
|
8572
8680
|
_guard_scalar('NavItem.label', label, (str,), False, False, False)
|
|
8573
8681
|
_guard_scalar('NavItem.icon', icon, (str,), False, True, False)
|
|
8574
8682
|
_guard_scalar('NavItem.disabled', disabled, (bool,), False, True, False)
|
|
8575
8683
|
_guard_scalar('NavItem.tooltip', tooltip, (str,), False, True, False)
|
|
8684
|
+
_guard_scalar('NavItem.path', path, (str,), False, True, False)
|
|
8576
8685
|
self.name = name
|
|
8577
8686
|
"""The name of this item. Prefix the name with a '#' to trigger hash-change navigation."""
|
|
8578
8687
|
self.label = label
|
|
@@ -8583,6 +8692,8 @@ class NavItem:
|
|
|
8583
8692
|
"""True if this item should be disabled."""
|
|
8584
8693
|
self.tooltip = tooltip
|
|
8585
8694
|
"""An optional tooltip message displayed when a user hovers over this item."""
|
|
8695
|
+
self.path = path
|
|
8696
|
+
"""The path or URL to link to. If specified, the `name` is ignored. The URL is opened in a new browser window or tab. E.g. `/foo.html` or `http://example.com/foo.html`"""
|
|
8586
8697
|
|
|
8587
8698
|
def dump(self) -> Dict:
|
|
8588
8699
|
"""Returns the contents of this object as a dict."""
|
|
@@ -8591,12 +8702,14 @@ class NavItem:
|
|
|
8591
8702
|
_guard_scalar('NavItem.icon', self.icon, (str,), False, True, False)
|
|
8592
8703
|
_guard_scalar('NavItem.disabled', self.disabled, (bool,), False, True, False)
|
|
8593
8704
|
_guard_scalar('NavItem.tooltip', self.tooltip, (str,), False, True, False)
|
|
8705
|
+
_guard_scalar('NavItem.path', self.path, (str,), False, True, False)
|
|
8594
8706
|
return _dump(
|
|
8595
8707
|
name=self.name,
|
|
8596
8708
|
label=self.label,
|
|
8597
8709
|
icon=self.icon,
|
|
8598
8710
|
disabled=self.disabled,
|
|
8599
8711
|
tooltip=self.tooltip,
|
|
8712
|
+
path=self.path,
|
|
8600
8713
|
)
|
|
8601
8714
|
|
|
8602
8715
|
@staticmethod
|
|
@@ -8612,17 +8725,21 @@ class NavItem:
|
|
|
8612
8725
|
_guard_scalar('NavItem.disabled', __d_disabled, (bool,), False, True, False)
|
|
8613
8726
|
__d_tooltip: Any = __d.get('tooltip')
|
|
8614
8727
|
_guard_scalar('NavItem.tooltip', __d_tooltip, (str,), False, True, False)
|
|
8728
|
+
__d_path: Any = __d.get('path')
|
|
8729
|
+
_guard_scalar('NavItem.path', __d_path, (str,), False, True, False)
|
|
8615
8730
|
name: str = __d_name
|
|
8616
8731
|
label: str = __d_label
|
|
8617
8732
|
icon: Optional[str] = __d_icon
|
|
8618
8733
|
disabled: Optional[bool] = __d_disabled
|
|
8619
8734
|
tooltip: Optional[str] = __d_tooltip
|
|
8735
|
+
path: Optional[str] = __d_path
|
|
8620
8736
|
return NavItem(
|
|
8621
8737
|
name,
|
|
8622
8738
|
label,
|
|
8623
8739
|
icon,
|
|
8624
8740
|
disabled,
|
|
8625
8741
|
tooltip,
|
|
8742
|
+
path,
|
|
8626
8743
|
)
|
|
8627
8744
|
|
|
8628
8745
|
|
|
@@ -9368,11 +9485,13 @@ class MarkupCard:
|
|
|
9368
9485
|
box: str,
|
|
9369
9486
|
title: str,
|
|
9370
9487
|
content: str,
|
|
9488
|
+
compact: Optional[bool] = None,
|
|
9371
9489
|
commands: Optional[List[Command]] = None,
|
|
9372
9490
|
):
|
|
9373
9491
|
_guard_scalar('MarkupCard.box', box, (str,), False, False, False)
|
|
9374
9492
|
_guard_scalar('MarkupCard.title', title, (str,), False, False, False)
|
|
9375
9493
|
_guard_scalar('MarkupCard.content', content, (str,), False, False, False)
|
|
9494
|
+
_guard_scalar('MarkupCard.compact', compact, (bool,), False, True, False)
|
|
9376
9495
|
_guard_vector('MarkupCard.commands', commands, (Command,), False, True, False)
|
|
9377
9496
|
self.box = box
|
|
9378
9497
|
"""A string indicating how to place this component on the page."""
|
|
@@ -9380,6 +9499,8 @@ class MarkupCard:
|
|
|
9380
9499
|
"""The title for this card."""
|
|
9381
9500
|
self.content = content
|
|
9382
9501
|
"""The HTML content."""
|
|
9502
|
+
self.compact = compact
|
|
9503
|
+
"""True if outer spacing should be removed. Defaults to False."""
|
|
9383
9504
|
self.commands = commands
|
|
9384
9505
|
"""Contextual menu commands for this component."""
|
|
9385
9506
|
|
|
@@ -9388,12 +9509,14 @@ class MarkupCard:
|
|
|
9388
9509
|
_guard_scalar('MarkupCard.box', self.box, (str,), False, False, False)
|
|
9389
9510
|
_guard_scalar('MarkupCard.title', self.title, (str,), False, False, False)
|
|
9390
9511
|
_guard_scalar('MarkupCard.content', self.content, (str,), False, False, False)
|
|
9512
|
+
_guard_scalar('MarkupCard.compact', self.compact, (bool,), False, True, False)
|
|
9391
9513
|
_guard_vector('MarkupCard.commands', self.commands, (Command,), False, True, False)
|
|
9392
9514
|
return _dump(
|
|
9393
9515
|
view='markup',
|
|
9394
9516
|
box=self.box,
|
|
9395
9517
|
title=self.title,
|
|
9396
9518
|
content=self.content,
|
|
9519
|
+
compact=self.compact,
|
|
9397
9520
|
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
|
|
9398
9521
|
)
|
|
9399
9522
|
|
|
@@ -9406,16 +9529,20 @@ class MarkupCard:
|
|
|
9406
9529
|
_guard_scalar('MarkupCard.title', __d_title, (str,), False, False, False)
|
|
9407
9530
|
__d_content: Any = __d.get('content')
|
|
9408
9531
|
_guard_scalar('MarkupCard.content', __d_content, (str,), False, False, False)
|
|
9532
|
+
__d_compact: Any = __d.get('compact')
|
|
9533
|
+
_guard_scalar('MarkupCard.compact', __d_compact, (bool,), False, True, False)
|
|
9409
9534
|
__d_commands: Any = __d.get('commands')
|
|
9410
9535
|
_guard_vector('MarkupCard.commands', __d_commands, (dict,), False, True, False)
|
|
9411
9536
|
box: str = __d_box
|
|
9412
9537
|
title: str = __d_title
|
|
9413
9538
|
content: str = __d_content
|
|
9539
|
+
compact: Optional[bool] = __d_compact
|
|
9414
9540
|
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
|
|
9415
9541
|
return MarkupCard(
|
|
9416
9542
|
box,
|
|
9417
9543
|
title,
|
|
9418
9544
|
content,
|
|
9545
|
+
compact,
|
|
9419
9546
|
commands,
|
|
9420
9547
|
)
|
|
9421
9548
|
|
h2o_wave/ui.py
CHANGED
|
@@ -998,6 +998,7 @@ def button(
|
|
|
998
998
|
visible: Optional[bool] = None,
|
|
999
999
|
tooltip: Optional[str] = None,
|
|
1000
1000
|
path: Optional[str] = None,
|
|
1001
|
+
commands: Optional[List[Command]] = None,
|
|
1001
1002
|
) -> Component:
|
|
1002
1003
|
"""Create a button.
|
|
1003
1004
|
|
|
@@ -1029,6 +1030,7 @@ def button(
|
|
|
1029
1030
|
visible: True if the component should be visible. Defaults to True.
|
|
1030
1031
|
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
|
|
1031
1032
|
path: The path or URL to link to. If specified, the `name` is ignored. The URL is opened in a new browser window or tab.
|
|
1033
|
+
commands: When specified, a split button is rendered with extra actions tied to it within a context menu. Mutually exclusive with `link` attribute.
|
|
1032
1034
|
Returns:
|
|
1033
1035
|
A `h2o_wave.types.Button` instance.
|
|
1034
1036
|
"""
|
|
@@ -1045,6 +1047,7 @@ def button(
|
|
|
1045
1047
|
visible,
|
|
1046
1048
|
tooltip,
|
|
1047
1049
|
path,
|
|
1050
|
+
commands,
|
|
1048
1051
|
))
|
|
1049
1052
|
|
|
1050
1053
|
|
|
@@ -1316,7 +1319,7 @@ def table_column(
|
|
|
1316
1319
|
data_type: Defines the data type of this column. Time column takes either ISO 8601 date string or unix epoch miliseconds. Defaults to `string`. One of 'string', 'number', 'time'. See enum h2o_wave.ui.TableColumnDataType.
|
|
1317
1320
|
cell_type: Defines how to render each cell in this column. Renders as plain text by default.
|
|
1318
1321
|
cell_overflow: Defines what to do with a cell's contents in case it does not fit inside the cell. One of 'tooltip', 'wrap'. See enum h2o_wave.ui.TableColumnCellOverflow.
|
|
1319
|
-
filters:
|
|
1322
|
+
filters: Explicit list of values to allow filtering by, needed when pagination is set or custom order is needed. Only applicable to filterable columns.
|
|
1320
1323
|
align: Defines how to align values in a column. One of 'left', 'center', 'right'. See enum h2o_wave.ui.TableColumnAlign.
|
|
1321
1324
|
Returns:
|
|
1322
1325
|
A `h2o_wave.types.TableColumn` instance.
|
|
@@ -1445,7 +1448,7 @@ def table(
|
|
|
1445
1448
|
groupable: True to allow group by feature.
|
|
1446
1449
|
downloadable: Indicates whether the table rows can be downloaded as a CSV file. Defaults to False.
|
|
1447
1450
|
resettable: Indicates whether a Reset button should be displayed to reset search / filter / group-by values to their defaults. Defaults to False.
|
|
1448
|
-
height: The height of the table
|
|
1451
|
+
height: The height of the table in px (e.g. '200px') or '1' to fill the remaining card space.
|
|
1449
1452
|
width: The width of the table, e.g. '100px'. Defaults to '100%'.
|
|
1450
1453
|
values: The names of the selected rows. If this parameter is set, multiple selections will be allowed (`multiple` is assumed to be `True`).
|
|
1451
1454
|
checkbox_visibility: Controls visibility of table rows when `multiple` is set to `True`. Defaults to 'on-hover'. One of 'always', 'on-hover', 'hidden'. See enum h2o_wave.ui.TableCheckboxVisibility.
|
|
@@ -1453,7 +1456,7 @@ def table(
|
|
|
1453
1456
|
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
|
|
1454
1457
|
groups: Creates collapsible / expandable groups of data rows. Mutually exclusive with `rows` attr.
|
|
1455
1458
|
pagination: Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`.
|
|
1456
|
-
events: The events to capture on this table. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'.
|
|
1459
|
+
events: The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'.
|
|
1457
1460
|
single: True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr.
|
|
1458
1461
|
value: The name of the selected row. If this parameter is set, single selection will be allowed (`single` is assumed to be `True`).
|
|
1459
1462
|
Returns:
|
|
@@ -2479,6 +2482,7 @@ def image_annotator(
|
|
|
2479
2482
|
trigger: Optional[bool] = None,
|
|
2480
2483
|
image_height: Optional[str] = None,
|
|
2481
2484
|
allowed_shapes: Optional[List[str]] = None,
|
|
2485
|
+
events: Optional[List[str]] = None,
|
|
2482
2486
|
) -> Component:
|
|
2483
2487
|
"""Create an image annotator component.
|
|
2484
2488
|
|
|
@@ -2493,6 +2497,7 @@ def image_annotator(
|
|
|
2493
2497
|
trigger: True if the form should be submitted as soon as an annotation is drawn.
|
|
2494
2498
|
image_height: The card’s image height. The actual image size is used by default.
|
|
2495
2499
|
allowed_shapes: List of allowed shapes. Available values are 'rect' and 'polygon'. If not set, all shapes are available by default.
|
|
2500
|
+
events: The events to capture on this image annotator. One of `click` or `tool_change`.
|
|
2496
2501
|
Returns:
|
|
2497
2502
|
A `h2o_wave.types.ImageAnnotator` instance.
|
|
2498
2503
|
"""
|
|
@@ -2505,6 +2510,7 @@ def image_annotator(
|
|
|
2505
2510
|
trigger,
|
|
2506
2511
|
image_height,
|
|
2507
2512
|
allowed_shapes,
|
|
2513
|
+
events,
|
|
2508
2514
|
))
|
|
2509
2515
|
|
|
2510
2516
|
|
|
@@ -2548,7 +2554,7 @@ def copyable_text(
|
|
|
2548
2554
|
label: The text displayed above the textbox.
|
|
2549
2555
|
name: An identifying name for this component.
|
|
2550
2556
|
multiline: True if the component should allow multi-line text entry.
|
|
2551
|
-
height: Custom height in px
|
|
2557
|
+
height: Custom height in px (e.g. '200px') or '1' to fill the remaining card space. Requires `multiline` to be set.
|
|
2552
2558
|
Returns:
|
|
2553
2559
|
A `h2o_wave.types.CopyableText` instance.
|
|
2554
2560
|
"""
|
|
@@ -2791,6 +2797,39 @@ def chat_card(
|
|
|
2791
2797
|
)
|
|
2792
2798
|
|
|
2793
2799
|
|
|
2800
|
+
def chatbot_card(
|
|
2801
|
+
box: str,
|
|
2802
|
+
name: str,
|
|
2803
|
+
data: PackedRecord,
|
|
2804
|
+
placeholder: Optional[str] = None,
|
|
2805
|
+
events: Optional[List[str]] = None,
|
|
2806
|
+
generating: Optional[bool] = None,
|
|
2807
|
+
commands: Optional[List[Command]] = None,
|
|
2808
|
+
) -> ChatbotCard:
|
|
2809
|
+
"""Create a chatbot card to allow getting prompts from users and providing them with LLM generated answers.
|
|
2810
|
+
|
|
2811
|
+
Args:
|
|
2812
|
+
box: A string indicating how to place this component on the page.
|
|
2813
|
+
name: An identifying name for this component.
|
|
2814
|
+
data: Chat messages data. Requires cyclic buffer.
|
|
2815
|
+
placeholder: Chat input box placeholder. Use for prompt examples.
|
|
2816
|
+
events: The events to capture on this chatbot. One of 'stop'.
|
|
2817
|
+
generating: True to show a button to stop the text generation. Defaults to False.
|
|
2818
|
+
commands: Contextual menu commands for this component.
|
|
2819
|
+
Returns:
|
|
2820
|
+
A `h2o_wave.types.ChatbotCard` instance.
|
|
2821
|
+
"""
|
|
2822
|
+
return ChatbotCard(
|
|
2823
|
+
box,
|
|
2824
|
+
name,
|
|
2825
|
+
data,
|
|
2826
|
+
placeholder,
|
|
2827
|
+
events,
|
|
2828
|
+
generating,
|
|
2829
|
+
commands,
|
|
2830
|
+
)
|
|
2831
|
+
|
|
2832
|
+
|
|
2794
2833
|
def editor_card(
|
|
2795
2834
|
box: str,
|
|
2796
2835
|
mode: str,
|
|
@@ -3003,6 +3042,7 @@ def nav_item(
|
|
|
3003
3042
|
icon: Optional[str] = None,
|
|
3004
3043
|
disabled: Optional[bool] = None,
|
|
3005
3044
|
tooltip: Optional[str] = None,
|
|
3045
|
+
path: Optional[str] = None,
|
|
3006
3046
|
) -> NavItem:
|
|
3007
3047
|
"""Create a navigation item.
|
|
3008
3048
|
|
|
@@ -3012,6 +3052,7 @@ def nav_item(
|
|
|
3012
3052
|
icon: An optional icon to display next to the label.
|
|
3013
3053
|
disabled: True if this item should be disabled.
|
|
3014
3054
|
tooltip: An optional tooltip message displayed when a user hovers over this item.
|
|
3055
|
+
path: The path or URL to link to. If specified, the `name` is ignored. The URL is opened in a new browser window or tab. E.g. `/foo.html` or `http://example.com/foo.html`
|
|
3015
3056
|
Returns:
|
|
3016
3057
|
A `h2o_wave.types.NavItem` instance.
|
|
3017
3058
|
"""
|
|
@@ -3021,6 +3062,7 @@ def nav_item(
|
|
|
3021
3062
|
icon,
|
|
3022
3063
|
disabled,
|
|
3023
3064
|
tooltip,
|
|
3065
|
+
path,
|
|
3024
3066
|
)
|
|
3025
3067
|
|
|
3026
3068
|
|
|
@@ -3305,6 +3347,7 @@ def markup_card(
|
|
|
3305
3347
|
box: str,
|
|
3306
3348
|
title: str,
|
|
3307
3349
|
content: str,
|
|
3350
|
+
compact: Optional[bool] = None,
|
|
3308
3351
|
commands: Optional[List[Command]] = None,
|
|
3309
3352
|
) -> MarkupCard:
|
|
3310
3353
|
"""Render HTML content.
|
|
@@ -3313,6 +3356,7 @@ def markup_card(
|
|
|
3313
3356
|
box: A string indicating how to place this component on the page.
|
|
3314
3357
|
title: The title for this card.
|
|
3315
3358
|
content: The HTML content.
|
|
3359
|
+
compact: True if outer spacing should be removed. Defaults to False.
|
|
3316
3360
|
commands: Contextual menu commands for this component.
|
|
3317
3361
|
Returns:
|
|
3318
3362
|
A `h2o_wave.types.MarkupCard` instance.
|
|
@@ -3321,6 +3365,7 @@ def markup_card(
|
|
|
3321
3365
|
box,
|
|
3322
3366
|
title,
|
|
3323
3367
|
content,
|
|
3368
|
+
compact,
|
|
3324
3369
|
commands,
|
|
3325
3370
|
)
|
|
3326
3371
|
|
h2o_wave/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.
|
|
1
|
+
__version__ = '0.26.1'
|
|
@@ -64,6 +64,6 @@ If you have any questions, please reach out to [Github Discussions](https://gith
|
|
|
64
64
|
# Update state
|
|
65
65
|
q.client.count += 1
|
|
66
66
|
# Update the button label.
|
|
67
|
-
q.page['body'].
|
|
67
|
+
q.page['body'].counter.label = f'Current count: {q.client.count}'
|
|
68
68
|
|
|
69
69
|
await q.page.save()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
h2o_wave/__init__.py,sha256=OOFxoUhRs7IHsmZZyDGAkY4_sdlUtuJJQT1x06Adkn4,1674
|
|
2
|
+
h2o_wave/__main__.py,sha256=MoNOW43ppIqCdY3iq0n25Q3SKLyk8Igg5fD_sSROK4c,638
|
|
3
|
+
h2o_wave/cli.py,sha256=z8Th8DmEkTkhiJeMBu3vX5UbbfymjQgDlcUOKcd2Hy4,12444
|
|
4
|
+
h2o_wave/core.py,sha256=YEHBFWTllX99jTofGWPOg93MavLsOonTT6gGiciqp7U,39019
|
|
5
|
+
h2o_wave/db.py,sha256=H3W_EyMfnwr4UjqPVoAsE19O2QzY1ptIYGMOqU0YUQo,7489
|
|
6
|
+
h2o_wave/graphics.py,sha256=HLYrX-lwsMKbyLmy2ClG5L46DA2_hSCEPTsv0gPVoyg,25866
|
|
7
|
+
h2o_wave/ide.py,sha256=wAJjfEI1ekQrL455FGRVeNi3KiH6ELgsG3qm31Q-kRs,6810
|
|
8
|
+
h2o_wave/metadata.py,sha256=yxQ-BCY56rDs8o3N8K_rOABA5h390WZs8C0uXJy5sDw,66
|
|
9
|
+
h2o_wave/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
h2o_wave/routing.py,sha256=5aNgnVjhYU3Yih--rC2THfsNZpVWBHmzY9ju64KtyB4,7889
|
|
11
|
+
h2o_wave/server.py,sha256=a-p7WVtEz5GSbjSzMxO9sY92AoHWalQiqmHR-2evXH8,19122
|
|
12
|
+
h2o_wave/share.py,sha256=nnLwLt7cccUMMFWUkVPUgiv2-T_2pcOxlVfGbl-btv4,843
|
|
13
|
+
h2o_wave/test.py,sha256=hF_fS5e25bACnzENjpDrikv7nPOs0iENh4MuXX9BaVA,2738
|
|
14
|
+
h2o_wave/types.py,sha256=DO-z92m6oAxPUhN1144vdZ4Ug8jII8YAGlHYu239P4E,635848
|
|
15
|
+
h2o_wave/ui.py,sha256=u41t97teUgPOB3IJ8TxIkd4qp8AW_Y9Dskcaq78iRww,166206
|
|
16
|
+
h2o_wave/ui_ext.py,sha256=zx_2Ec2-p_ztm8brfVaVF0fTQWVDrb_YxcGfVb-wA10,2325
|
|
17
|
+
h2o_wave/version.py,sha256=ETW1KUvXcKPocfR8oqFVOl2UoxVGnBdqQHPWngZJk_g,23
|
|
18
|
+
h2o_wave-0.26.1.data/data/project_templates/README.md,sha256=RFQX5Ly7sZd55zRW3GgqjJDMFoPbZbZM7zuK1CuP1y4,1052
|
|
19
|
+
h2o_wave-0.26.1.data/data/project_templates/header.py,sha256=lIROU6xzlzxq2GrqiUVYdqJ3U7zRln4qbehQipdfVxQ,2603
|
|
20
|
+
h2o_wave-0.26.1.data/data/project_templates/header_nav.py,sha256=3ctwel84C2Kh05s_IR9COBcLYjSUWuh-LcmJyNfzWyM,10389
|
|
21
|
+
h2o_wave-0.26.1.data/data/project_templates/header_sidebar_nav.py,sha256=bJWlfuQkFqGy4kXPvLXfZ5IOxDMmR7IAFWh-CITMMqg,10994
|
|
22
|
+
h2o_wave-0.26.1.data/data/project_templates/hello_world.py,sha256=wUHq-f9bXcVXDyGTHKYI7i44mK9tlbAJIqFEytTBB2I,2475
|
|
23
|
+
h2o_wave-0.26.1.data/data/project_templates/sidebar_nav.py,sha256=Iq-CY5VSPXibruN_Qu_Jh6f6DIQ3Q9Mr90HWyKdmiF0,10775
|
|
24
|
+
h2o_wave-0.26.1.dist-info/LICENSE,sha256=hpuFayniDwysSKD0tHGELH2KJDVyhUrKS29torRIpqY,53
|
|
25
|
+
h2o_wave-0.26.1.dist-info/METADATA,sha256=cqqxZPcCZ2qmsoJC9lWhCPU5ZndiYc5mx8qcZF7ZkBo,2560
|
|
26
|
+
h2o_wave-0.26.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
27
|
+
h2o_wave-0.26.1.dist-info/entry_points.txt,sha256=0JfvaphzVeSD5pkJTr6OKTLcROFnQ1cp31DI8M5frzY,44
|
|
28
|
+
h2o_wave-0.26.1.dist-info/top_level.txt,sha256=jmpW_e6CkJf82dE4S7ofAe2ipbBd1YmrS-0MM-DN0lY,9
|
|
29
|
+
h2o_wave-0.26.1.dist-info/RECORD,,
|
h2o_wave-0.25.3.dist-info/RECORD
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
h2o_wave/__init__.py,sha256=OOFxoUhRs7IHsmZZyDGAkY4_sdlUtuJJQT1x06Adkn4,1674
|
|
2
|
-
h2o_wave/__main__.py,sha256=MoNOW43ppIqCdY3iq0n25Q3SKLyk8Igg5fD_sSROK4c,638
|
|
3
|
-
h2o_wave/cli.py,sha256=F-Xv8LACTNvZjPf2RIMBsbWZizmMOERtfvhMry3I6Kk,9259
|
|
4
|
-
h2o_wave/core.py,sha256=M_d6Ea2qYgUaQMyMuynLci8B26HODBYUWCt4FX4-cHY,38046
|
|
5
|
-
h2o_wave/db.py,sha256=H3W_EyMfnwr4UjqPVoAsE19O2QzY1ptIYGMOqU0YUQo,7489
|
|
6
|
-
h2o_wave/graphics.py,sha256=HLYrX-lwsMKbyLmy2ClG5L46DA2_hSCEPTsv0gPVoyg,25866
|
|
7
|
-
h2o_wave/ide.py,sha256=wAJjfEI1ekQrL455FGRVeNi3KiH6ELgsG3qm31Q-kRs,6810
|
|
8
|
-
h2o_wave/metadata.py,sha256=yxQ-BCY56rDs8o3N8K_rOABA5h390WZs8C0uXJy5sDw,66
|
|
9
|
-
h2o_wave/routing.py,sha256=hfxb33dpK5MvIEvxdPyodjTkMLH-anP9FG3Xl1_h4n4,7895
|
|
10
|
-
h2o_wave/server.py,sha256=6Q97KM1ROv0SGlXK9URUmwe-fh9S8IRBUTyqmllaqYs,19018
|
|
11
|
-
h2o_wave/test.py,sha256=hF_fS5e25bACnzENjpDrikv7nPOs0iENh4MuXX9BaVA,2738
|
|
12
|
-
h2o_wave/types.py,sha256=ZaYdqL60TA1UXnEVBGKUefVPjS2sKiiHXL9Ch9n06mE,629082
|
|
13
|
-
h2o_wave/ui.py,sha256=sf7VQAt7G7qqLeDa9MqyVv_PLrhy341b72Sag0TdoCg,164224
|
|
14
|
-
h2o_wave/ui_ext.py,sha256=zx_2Ec2-p_ztm8brfVaVF0fTQWVDrb_YxcGfVb-wA10,2325
|
|
15
|
-
h2o_wave/version.py,sha256=_duDn5pwjRE5QtlNYg8tB-1mE-qM0W8f4UeMVqrkU8U,23
|
|
16
|
-
h2o_wave-0.25.3.data/data/project_templates/README.md,sha256=RFQX5Ly7sZd55zRW3GgqjJDMFoPbZbZM7zuK1CuP1y4,1052
|
|
17
|
-
h2o_wave-0.25.3.data/data/project_templates/header.py,sha256=lIROU6xzlzxq2GrqiUVYdqJ3U7zRln4qbehQipdfVxQ,2603
|
|
18
|
-
h2o_wave-0.25.3.data/data/project_templates/header_nav.py,sha256=3ctwel84C2Kh05s_IR9COBcLYjSUWuh-LcmJyNfzWyM,10389
|
|
19
|
-
h2o_wave-0.25.3.data/data/project_templates/header_sidebar_nav.py,sha256=bJWlfuQkFqGy4kXPvLXfZ5IOxDMmR7IAFWh-CITMMqg,10994
|
|
20
|
-
h2o_wave-0.25.3.data/data/project_templates/hello_world.py,sha256=DmWwgdI2BdsnHZ7ai_4twNp0RzGdkTLoLF-F9tRGzS8,2483
|
|
21
|
-
h2o_wave-0.25.3.data/data/project_templates/sidebar_nav.py,sha256=Iq-CY5VSPXibruN_Qu_Jh6f6DIQ3Q9Mr90HWyKdmiF0,10775
|
|
22
|
-
h2o_wave-0.25.3.dist-info/LICENSE,sha256=hpuFayniDwysSKD0tHGELH2KJDVyhUrKS29torRIpqY,53
|
|
23
|
-
h2o_wave-0.25.3.dist-info/METADATA,sha256=MEp59SBlzLENLOruorieQHOIubEQHtd6BLXOT4oebd0,2560
|
|
24
|
-
h2o_wave-0.25.3.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
25
|
-
h2o_wave-0.25.3.dist-info/entry_points.txt,sha256=0JfvaphzVeSD5pkJTr6OKTLcROFnQ1cp31DI8M5frzY,44
|
|
26
|
-
h2o_wave-0.25.3.dist-info/top_level.txt,sha256=jmpW_e6CkJf82dE4S7ofAe2ipbBd1YmrS-0MM-DN0lY,9
|
|
27
|
-
h2o_wave-0.25.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|