dashlab 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {dashlab-0.3.0 → dashlab-0.3.2}/PKG-INFO +1 -1
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/_version.py +1 -1
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/base.py +84 -64
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/listw.css +4 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/PKG-INFO +1 -1
- {dashlab-0.3.0 → dashlab-0.3.2}/LICENSE +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/README.md +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/__init__.py +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/_internal.py +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/core.py +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/patches.py +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/animator.css +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/animator.js +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/fscreen.css +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/fscreen.js +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/listw.js +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/utils.py +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/widgets.py +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/SOURCES.txt +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/dependency_links.txt +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/requires.txt +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/top_level.txt +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/pyproject.toml +0 -0
- {dashlab-0.3.0 → dashlab-0.3.2}/setup.cfg +0 -0
|
@@ -185,14 +185,14 @@ _docs = {
|
|
|
185
185
|
- Built-in fullscreen support
|
|
186
186
|
""",
|
|
187
187
|
"css_info": re.sub(r'\bcode(\[.*?\])?\`', '`', _build_css.__doc__, flags=re.DOTALL), # inline code` or code['css']` not supported is dashlab itself
|
|
188
|
-
"gather": """
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"""
|
|
188
|
+
"gather": """
|
|
189
|
+
- Name of widgets from params or output widgets from callbacks by their CSS class names (e.g. 'out-stats').
|
|
190
|
+
- Special group names: `*all`, `*out`, `*ctrl`, `*repr` for all widgets, outputs, controls, representation widgets respectively.
|
|
191
|
+
- Special groups support exclusion patterns with '!' suffix (e.g. '*all!debug.*', '*ctrl!btn.*' to exclude specific widgets or regex patterns from the group).
|
|
192
|
+
- Exclusion patterns can be exact names or regex patterns and are applied only to the widgets in the specified group.
|
|
193
|
+
- Regex patterns to match full widget names (e.g. 'fig.*' to match 'fig1', 'fig2' etc.). Only raises error if regex is invalid, not if no matches found.
|
|
194
|
+
- Direct DOMWidget instances can also be passed inside list in any order to include external widgets not in params/outputs.
|
|
195
|
+
""",
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
def _expose_widget(v):
|
|
@@ -420,47 +420,58 @@ class DashboardBase(ipw.interactive, metaclass = _metaclass):
|
|
|
420
420
|
raise type(e)(f"In {key!r} of layout: {e}") from e # better error message
|
|
421
421
|
return layout_widgets
|
|
422
422
|
|
|
423
|
+
def __unpack_group(self, pattern):
|
|
424
|
+
group, excp = pattern.split('!',1) if '!' in pattern else (pattern, '')
|
|
425
|
+
exclude = []
|
|
426
|
+
if excp.strip(): # only try to exclude if excp is not empty or just whitespace
|
|
427
|
+
try:
|
|
428
|
+
exclude = [name for name in self.__all_widgets if re.fullmatch(excp, name)]
|
|
429
|
+
except re.error as e:
|
|
430
|
+
raise ValueError(f"Invalid exclusion regex pattern {excp!r} in {pattern!r}.\n{e}") from e
|
|
431
|
+
|
|
432
|
+
if not group in self.__groups:
|
|
433
|
+
raise ValueError(f"Invalid special group name {group!r}, valid names are: {list(self.__groups)} followed by optional '!name|regex...' exclusion")
|
|
434
|
+
return [name for name in self.__groups[group] if name not in exclude]
|
|
435
|
+
|
|
423
436
|
@_format_docs(**_docs)
|
|
424
|
-
def gather(self, *widgets: 'str | DOMWidget') -> tuple[DOMWidget]:
|
|
437
|
+
def gather(self, *widgets: 'str | DOMWidget', verbose: bool=False) -> tuple[DOMWidget]:
|
|
425
438
|
"""Get list of widgets by names or general widgets for layout configuration.
|
|
426
439
|
This can be used to collect widgets to embed at any nesting level in layout.
|
|
427
440
|
|
|
428
441
|
**Parameters** (str | DOMWidget):
|
|
429
442
|
{gather}
|
|
430
|
-
|
|
443
|
+
Use `verbose=True` to print matched widgets for each pattern to ensure correct matching.
|
|
444
|
+
|
|
445
|
+
**Returns**: List of DOMWidget instances corresponding to the provided names or instances.
|
|
431
446
|
|
|
432
447
|
**Example**:
|
|
433
448
|
|
|
434
449
|
```python
|
|
435
|
-
#
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
#
|
|
450
|
+
# Basic usage
|
|
451
|
+
widgets = dash.gather('fig1', 'fig2', 'out-stats')
|
|
452
|
+
|
|
453
|
+
# Special groups
|
|
454
|
+
all_controls = dash.gather('*ctrl')
|
|
455
|
+
all_outputs = dash.gather('*out')
|
|
456
|
+
|
|
457
|
+
# Groups with exclusions
|
|
458
|
+
controls_no_buttons = dash.gather('*ctrl!btn.*')
|
|
459
|
+
all_except_debug = dash.gather('*all!.*debug.*')
|
|
460
|
+
|
|
461
|
+
# Regex patterns
|
|
462
|
+
fig_widgets = dash.gather('fig.*') # fig1, fig2, fig_debug
|
|
463
|
+
numbered = dash.gather('.*[0-9].*') # any widget with numbers at end
|
|
464
|
+
|
|
465
|
+
# Mixed patterns
|
|
466
|
+
result = dash.gather('fig1', '*ctrl!btn.*', external_widget)
|
|
467
|
+
|
|
468
|
+
# Verbose output with colors
|
|
469
|
+
widgets = dash.gather('*all!debug.*', verbose=True)
|
|
470
|
+
# Shows: [gather (group)]: *all!debug.* → fig1,fig2,x,y,out-stats
|
|
443
471
|
```
|
|
444
472
|
"""
|
|
445
|
-
specials =
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
# First collect all excluded names
|
|
449
|
-
exclude = set()
|
|
450
|
-
for name in widgets:
|
|
451
|
-
if isinstance(name, str) and name.startswith('!'):
|
|
452
|
-
exp = name[1:] # Remove '!' prefix
|
|
453
|
-
if not exp.strip(): # Handle empty exclusion patterns
|
|
454
|
-
raise ValueError(f"Invalid exclusion pattern {name!r} - empty patterns not allowed")
|
|
455
|
-
if exp in specials:
|
|
456
|
-
raise ValueError(f"Exclusion pattern {name!r} cannot be a special group name, use '!widget-name' or regex patterns prefixed with '!'.")
|
|
457
|
-
if exp in Ws:
|
|
458
|
-
exclude.add(exp) # Exact name exclusion
|
|
459
|
-
else: # Regex exclusion pattern - check against all widget names
|
|
460
|
-
try:
|
|
461
|
-
exclude.update([wname for wname in Ws.keys() if re.fullmatch(exp, wname)])
|
|
462
|
-
except re.error as e:
|
|
463
|
-
raise ValueError(f"Invalid regex pattern in exclusion {name!r}: {e}")
|
|
473
|
+
specials = list(self.__groups) # special group names
|
|
474
|
+
Ws, LC = self.__all_widgets, [name.lower() for name in self.__all_widgets] # all widgets by name and lower case for case insensitive search
|
|
464
475
|
|
|
465
476
|
collected = [] # And collect included names keeping exluded out
|
|
466
477
|
for name in widgets:
|
|
@@ -468,39 +479,47 @@ class DashboardBase(ipw.interactive, metaclass = _metaclass):
|
|
|
468
479
|
if not name.strip(): # Handle empty strings and whitespace
|
|
469
480
|
raise ValueError(f"Invalid widget name {name!r} - empty strings not allowed")
|
|
470
481
|
|
|
471
|
-
if name
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
482
|
+
if name in Ws: # catch all names without regex first and exlcuded above
|
|
483
|
+
collected.append(Ws[name])
|
|
484
|
+
if verbose:
|
|
485
|
+
print(f"\033[92m[gather (exact)]\033[0m: {name} → {name}")
|
|
486
|
+
elif name.lower() in LC: # case insensitive match
|
|
487
|
+
raise ValueError(f"Widget name {name!r} not found, did you mean {list(Ws)[LC.index(name.lower())]!r}? Widget names are case-sensitive.")
|
|
476
488
|
elif name.startswith('*'): # special groups
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
collected.extend([Ws[n] for n in
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
collected.extend([Ws[n] for n in Gs.others if n not in exclude])
|
|
485
|
-
else:
|
|
486
|
-
raise ValueError(f"Invalid special group name {name!r}, valid names are: {specials}")
|
|
489
|
+
names = []
|
|
490
|
+
try:
|
|
491
|
+
names = self.__unpack_group(name)
|
|
492
|
+
collected.extend([Ws[n] for n in names]) # already filtered above
|
|
493
|
+
finally:
|
|
494
|
+
if verbose:
|
|
495
|
+
print(f"\033[94m[gather (group)]\033[0m: {name} → {','.join(names) if names else 'No matches'}")
|
|
487
496
|
else:
|
|
497
|
+
matches = []
|
|
488
498
|
try:
|
|
489
499
|
matches = [wname for wname in Ws.keys() if re.fullmatch(name, wname)]
|
|
490
|
-
|
|
500
|
+
if matches:
|
|
501
|
+
collected.extend([Ws[wname] for wname in matches])
|
|
502
|
+
elif name.isidentifier(): # if simple name, raise error
|
|
503
|
+
raise ValueError(f"Invalid widget name {name!r}. Valid names: {list(Ws.keys())}, specials: {specials}")
|
|
491
504
|
except re.error as e: # only raise error if regex is invalid, not if no matches found
|
|
492
505
|
raise ValueError(
|
|
493
506
|
f"Invalid widget name {name!r}.\n"
|
|
494
507
|
f"Valid names: {list(Ws.keys())}\n"
|
|
495
|
-
f"Special groups: {specials}\n"
|
|
496
|
-
f"regex patterns to match full name in params/outputs are also supported
|
|
508
|
+
f"Special groups: {specials}, optionally followed by exclusion '!name|regex...'\n"
|
|
509
|
+
f"regex patterns to match full name in params/outputs are also supported.\n{e}")
|
|
510
|
+
finally:
|
|
511
|
+
if verbose:
|
|
512
|
+
print(f"\033[93m[gather (regex)]\033[0m: {name} → {','.join(matches) if matches else 'No matches'}")
|
|
513
|
+
|
|
497
514
|
elif isinstance(name, ipw.DOMWidget):
|
|
498
515
|
collected.append(name)
|
|
516
|
+
if verbose:
|
|
517
|
+
print(f"\033[95m[gather (widget)]\033[0m: {type(name).__name__} → Direct widget")
|
|
499
518
|
else:
|
|
500
519
|
raise TypeError(f"Each item must be a string or DOMWidget, got {type(name).__name__}")
|
|
501
520
|
return tuple(collected)
|
|
502
521
|
|
|
503
|
-
@_format_docs(gather = textwrap.indent(_docs['gather'], '
|
|
522
|
+
@_format_docs(gather = textwrap.indent(_docs['gather'], ' '))
|
|
504
523
|
def set_layout(self,
|
|
505
524
|
header: 'list[str, DOMWidget] | DOMWidget' = None,
|
|
506
525
|
center: 'list[str, DOMWidget] | DOMWidget' = None,
|
|
@@ -520,17 +539,19 @@ class DashboardBase(ipw.interactive, metaclass = _metaclass):
|
|
|
520
539
|
|
|
521
540
|
**Parameters**:
|
|
522
541
|
|
|
523
|
-
- Content Areas (list
|
|
542
|
+
- Content Areas (list[str, DOMWidget] | DOMWidget):
|
|
524
543
|
- header: Widgets at top
|
|
525
544
|
- center: Main content area (uses CSS Grid)
|
|
526
545
|
- left_sidebar: Left side widgets
|
|
527
546
|
- right_sidebar: Right side widgets
|
|
528
547
|
- footer: Bottom widgets
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
548
|
+
|
|
549
|
+
Each of content areas expect `list[str, DOMWidget] | DOMWidget` of widgets/ params names if given. See details below:
|
|
550
|
+
|
|
551
|
+
- If a single widget is passed, it will be used directly. If None, the area will be hidden.
|
|
552
|
+
- To get params/outputs by names at a nesting level, use `gather()` method, e.g. `center = TabsWidget(dash.gather('fig', 'out-stats'))`
|
|
553
|
+
- If a list/tuple is passed, it will be wrapped in a VBox (except center which uses GridBox).{gather}
|
|
554
|
+
|
|
534
555
|
- Grid Properties:
|
|
535
556
|
- pane_widths: list[str] - Widths for [left, center, right]
|
|
536
557
|
- pane_heights: list[str] - Heights for [header, center, footer]
|
|
@@ -827,7 +848,6 @@ class DashboardBase(ipw.interactive, metaclass = _metaclass):
|
|
|
827
848
|
raise ValueError(f"Function {f.__name__!r} has parameters {extra_params} that are not defined in interactive params.")
|
|
828
849
|
|
|
829
850
|
def __create_groups(self, widgets_dict):
|
|
830
|
-
groups = namedtuple('WidgetGropus', ['controls', 'outputs', 'others'])
|
|
831
851
|
controls, outputs, others = [], [], []
|
|
832
852
|
for key, widget in widgets_dict.items():
|
|
833
853
|
if isinstance(widget, ipw.Output):
|
|
@@ -843,7 +863,7 @@ class DashboardBase(ipw.interactive, metaclass = _metaclass):
|
|
|
843
863
|
widgets_dict[c].add_class('widget-param').add_class('widget-control') # similar to widget-output added by ipywidgets
|
|
844
864
|
for o in others:
|
|
845
865
|
widgets_dict[o].add_class('widget-param') # what else
|
|
846
|
-
return
|
|
866
|
+
return {'*all': tuple(widgets_dict), '*ctrl': tuple(controls), '*out': tuple(outputs), '*repr': tuple(others)}
|
|
847
867
|
|
|
848
868
|
def __hint_btns_update(self, func):
|
|
849
869
|
func_params = {k:v for k,v in inspect.signature(func).parameters.items()}
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
flex-direction: row;
|
|
15
15
|
margin-bottom: 0;
|
|
16
16
|
box-sizing: border-box;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
overflow-x: hidden;
|
|
19
|
+
padding-left: 4px;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
.list-widget:hover .list-item {
|
|
@@ -106,6 +109,7 @@
|
|
|
106
109
|
.tabs-widget .list-widget.tabs {
|
|
107
110
|
border-radius: 4px 4px 0 0;
|
|
108
111
|
border-bottom: 1px inset #8884;
|
|
112
|
+
padding-left: 0;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
.tabs-widget .list-widget.tabs .list-item:hover {
|
|
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
|
|
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
|
|
File without changes
|