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.
Files changed (24) hide show
  1. {dashlab-0.3.0 → dashlab-0.3.2}/PKG-INFO +1 -1
  2. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/_version.py +1 -1
  3. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/base.py +84 -64
  4. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/listw.css +4 -0
  5. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/PKG-INFO +1 -1
  6. {dashlab-0.3.0 → dashlab-0.3.2}/LICENSE +0 -0
  7. {dashlab-0.3.0 → dashlab-0.3.2}/README.md +0 -0
  8. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/__init__.py +0 -0
  9. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/_internal.py +0 -0
  10. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/core.py +0 -0
  11. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/patches.py +0 -0
  12. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/animator.css +0 -0
  13. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/animator.js +0 -0
  14. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/fscreen.css +0 -0
  15. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/fscreen.js +0 -0
  16. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/static/listw.js +0 -0
  17. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/utils.py +0 -0
  18. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab/widgets.py +0 -0
  19. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/SOURCES.txt +0 -0
  20. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/dependency_links.txt +0 -0
  21. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/requires.txt +0 -0
  22. {dashlab-0.3.0 → dashlab-0.3.2}/dashlab.egg-info/top_level.txt +0 -0
  23. {dashlab-0.3.0 → dashlab-0.3.2}/pyproject.toml +0 -0
  24. {dashlab-0.3.0 → dashlab-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dashlab
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A Python package for dashboard and data visualization tools.
5
5
  Author-email: Abdul Saboor <asaboor.my@outlook.com>
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
1
  # This is automatically updated at build time, do not edit manually.
2
2
  # Double qoites are checked here
3
3
 
4
- __version__ = "0.3.0"
4
+ __version__ = "0.3.2"
@@ -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
- - 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
- - 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.
192
- - Exclusion patterns with '!' prefix (e.g. '!widget-name', '!out-.*' to exclude specific widgets or regex patterns).
193
- This takes precedence over inclusion and can not be used with special group names, e.g. '!*out' is invalid.
194
- - Direct DOMWidget instances can also be passed in any order to include external widgets not in params/outputs.
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
- **Returns**: List of DOMWidget instances corresponding to the provided names or instances.
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
- # fig is a FigureWidget param, out-stats is a callback output
436
- app.set_layout(
437
- left_sidebar = ['x','y'], # or dash.gather('x','y') is same, or if only two controls x,y then *ctrl works too
438
- center = app.TabsWidget(
439
- app.gether('fig', ipw.HTML('Showing Stats'), 'out-stats')
440
- ) # Not possible to pass names list directly inside a nested widget container, so use gather in nesting levels
441
- ) # app is instance of DashboardBase
442
- # Placed (FigureWidget, HTML, Output) at, can be used in set_layout
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 = ['*all','*out','*ctrl','*repr']
446
- Gs, Ws = self.__groups, self.__all_widgets
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.startswith('!'):
472
- continue # Skip exclusion patterns, already processed above
473
- elif name in Ws: # catch all names without regex first and exlcuded above
474
- if name not in exclude: # exact name inclusion
475
- collected.append(Ws[name])
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
- if name == '*all':
478
- collected.extend([Ws[k] for k in Ws.keys() if k not in exclude])
479
- elif name == '*out':
480
- collected.extend([Ws[n] for n in Gs.outputs if n not in exclude])
481
- elif name == '*ctrl':
482
- collected.extend([Ws[n] for n in Gs.controls if n not in exclude])
483
- elif name == '*repr':
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
- collected.extend([Ws[wname] for wname in matches if wname not in exclude])
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/tuple of widget names):
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
- - Each of above must be of type `list[str, DOMWidget] | DOMWidget` of widgets/ params names if given.
530
- - If a single widget is passed, it will be used directly, if a list/tuple is passed, it will be wrapped in a VBox (except center which uses GridBox).
531
- {gather}
532
- - If None, the area will be hidden.
533
- - To get params/outputs by names at a nesting level, use `gather()` method, e.g. `center = TabsWidget(dash.gather('fig', 'out-stats'))`
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 groups(controls=tuple(controls), outputs=tuple(outputs), others=tuple(others))
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 {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dashlab
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A Python package for dashboard and data visualization tools.
5
5
  Author-email: Abdul Saboor <asaboor.my@outlook.com>
6
6
  License-Expression: MIT
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