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 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 time
16
- import tarfile
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
- def fetch():
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__}-{__platform__}-{__arch__}'
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 circular buffers and zero for variable length buffers.
310
- data: Initial data. Must be either a key-row ``dict`` for variable-length buffers OR a row ``list`` for fixed-size and circular buffers.
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 isinstance(d, dict):
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
- else:
326
- if n < 0:
327
- return dict(c=dict(f=f, d=d))
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 n == 0:
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
- else:
334
- if n < 0:
335
- return dict(c=dict(f=f, n=-n))
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 circular buffers and zero for variable length buffers.
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/&lcub;fruit&rcub;')` 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.)
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 12 10',
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
- """List of values to allow filtering by, needed when pagination is set. Only applicable to filterable columns."""
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, e.g. '400px', '50%', etc."""
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, e.g. '200px'. Requires `multiline` to be set."""
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: List of values to allow filtering by, needed when pagination is set. Only applicable to filterable columns.
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, e.g. '400px', '50%', etc.
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, e.g. '200px'. Requires `multiline` to be set.
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.25.3'
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'].items[0].button.label = f'Current count: {q.client.count}'
67
+ q.page['body'].counter.label = f'Current count: {q.client.count}'
68
68
 
69
69
  await q.page.save()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: h2o-wave
3
- Version: 0.25.3
3
+ Version: 0.26.1
4
4
  Summary: Python driver for H2O Wave Realtime Apps
5
5
  Home-page: https://h2o.ai/products/h2o-wave
6
6
  Author: Prithvi Prabhu
@@ -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,,
@@ -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,,