sier2 0.35__tar.gz → 0.37__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.
Potentially problematic release.
This version of sier2 might be problematic. Click here for more details.
- {sier2-0.35 → sier2-0.37}/PKG-INFO +2 -2
- {sier2-0.35 → sier2-0.37}/pyproject.toml +3 -6
- {sier2-0.35 → sier2-0.37}/src/sier2/_block.py +19 -1
- {sier2-0.35 → sier2-0.37}/src/sier2/_config.py +28 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/_dag.py +1 -71
- sier2-0.37/src/sier2/panel/_chart.py +106 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/panel/_panel.py +4 -2
- {sier2-0.35 → sier2-0.37}/LICENSE +0 -0
- {sier2-0.35 → sier2-0.37}/README.rst +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/__init__.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/__main__.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/_library.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/_logger.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/_util.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/_version.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/panel/__init__.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/panel/_feedlogger.py +0 -0
- {sier2-0.35 → sier2-0.37}/src/sier2/panel/_panel_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sier2
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.37
|
|
4
4
|
Summary: Blocks of code that are executed in dags
|
|
5
5
|
Author: Algol60
|
|
6
6
|
Author-email: algol60 <algol60@users.noreply.github.com>
|
|
@@ -12,7 +12,7 @@ Classifier: Intended Audience :: Science/Research
|
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Topic :: Scientific/Engineering
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
-
Project-URL: Homepage, https://github.com/
|
|
15
|
+
Project-URL: Homepage, https://github.com/sier2/sier2
|
|
16
16
|
Description-Content-Type: text/x-rst
|
|
17
17
|
|
|
18
18
|
Sier2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sier2"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.37"
|
|
4
4
|
description = "Blocks of code that are executed in dags"
|
|
5
5
|
authors = [
|
|
6
6
|
{name="Algol60", email="algol60 <algol60@users.noreply.github.com>"}
|
|
@@ -21,20 +21,17 @@ classifiers = [
|
|
|
21
21
|
[dependencies]
|
|
22
22
|
python = "^3.11"
|
|
23
23
|
|
|
24
|
-
holoviews = ">=1.19.0"
|
|
25
24
|
panel = ">=1.4.4"
|
|
26
25
|
param = ">=2.1.0"
|
|
27
26
|
|
|
28
27
|
[[tool.mypy.overrides]]
|
|
29
28
|
module = [
|
|
30
|
-
"
|
|
31
|
-
"param",
|
|
32
|
-
"networkx",
|
|
29
|
+
"param"
|
|
33
30
|
]
|
|
34
31
|
ignore_missing_imports = true
|
|
35
32
|
|
|
36
33
|
[project.urls]
|
|
37
|
-
Homepage = "https://github.com/
|
|
34
|
+
Homepage = "https://github.com/sier2/sier2"
|
|
38
35
|
|
|
39
36
|
[build-system]
|
|
40
37
|
requires = ["poetry-core>=2.1.1"]
|
|
@@ -41,6 +41,20 @@ same input block be used immediately.) This causes the block's
|
|
|
41
41
|
Dag execution then continues as normal.
|
|
42
42
|
'''
|
|
43
43
|
|
|
44
|
+
_VISIBLE_DOC = '''If True, the block will be visible in a GUI.
|
|
45
|
+
|
|
46
|
+
A block may not need to be visible in a dag with a GUI. For example,
|
|
47
|
+
it may be applying a pre-defined filter, or running an algorithm that
|
|
48
|
+
takes an indeterminate amount of time. Setting this parameter to False
|
|
49
|
+
tells the GUI not display this block. Dag execution will otherwise
|
|
50
|
+
proceed as normal.
|
|
51
|
+
|
|
52
|
+
This is also useful if a GUI application only requires a single block.
|
|
53
|
+
A dag requires at least two blocks, because blocks can only be added
|
|
54
|
+
by connecting them to another block. By making one block a "dummy"
|
|
55
|
+
that is not visible, the GUI effectivly has a single block.
|
|
56
|
+
'''
|
|
57
|
+
|
|
44
58
|
class Block(param.Parameterized):
|
|
45
59
|
"""The base class for blocks.
|
|
46
60
|
|
|
@@ -82,17 +96,20 @@ class Block(param.Parameterized):
|
|
|
82
96
|
"""
|
|
83
97
|
|
|
84
98
|
block_pause_execution = param.Boolean(default=False, label='Pause execution', doc=_PAUSE_EXECUTION_DOC)
|
|
99
|
+
block_visible = param.Boolean(default=True, label='Display block', doc=_VISIBLE_DOC)
|
|
85
100
|
|
|
86
101
|
_block_state = param.String(default=BlockState.READY)
|
|
87
102
|
|
|
88
103
|
SIER2_KEY = '_sier2__key'
|
|
89
104
|
|
|
90
|
-
def __init__(self, *args, block_pause_execution: bool=False, block_doc: str|None=None, continue_label='Continue', **kwargs):
|
|
105
|
+
def __init__(self, *args, block_pause_execution: bool=False, block_visible: bool=True, block_doc: str|None=None, continue_label='Continue', **kwargs):
|
|
91
106
|
"""
|
|
92
107
|
Parameters
|
|
93
108
|
----------
|
|
94
109
|
block_pause_execution: bool
|
|
95
110
|
If True, ``prepare()`` is called and dag execution stops.
|
|
111
|
+
block_visible: bool
|
|
112
|
+
If True (the default), the block will be visible in a GUI.
|
|
96
113
|
block_doc: str|None
|
|
97
114
|
Markdown documentation that may displayed in the user interface.
|
|
98
115
|
"""
|
|
@@ -102,6 +119,7 @@ class Block(param.Parameterized):
|
|
|
102
119
|
raise BlockError(f'Class {self.__class__} must have a docstring')
|
|
103
120
|
|
|
104
121
|
self.block_pause_execution = block_pause_execution
|
|
122
|
+
self.block_visible = block_visible
|
|
105
123
|
self.block_doc = block_doc
|
|
106
124
|
self.continue_label = continue_label
|
|
107
125
|
# self._block_state = BlockState.READY
|
|
@@ -13,9 +13,37 @@ from typing import Any
|
|
|
13
13
|
CONFIG_UPDATE = 'config_update'
|
|
14
14
|
|
|
15
15
|
def _default_config_file():
|
|
16
|
+
"""Determine the location of the config file sier2.ini.
|
|
17
|
+
|
|
18
|
+
If the environment variable ``SIER2_INI`` is set,
|
|
19
|
+
it specifies the path of the config file.
|
|
20
|
+
|
|
21
|
+
Otherwise, if Windows, use ``$env:APPDATA/sier2/sier2.ini``.
|
|
22
|
+
|
|
23
|
+
Otherwise, if ``XDG_CONFIG_HOME`` is set, use ``$XDG_CONFIG_HOME/sier2/sier2.ini``.
|
|
24
|
+
|
|
25
|
+
Otherwise, use ``$HOME/.config/sier2/sier2.ini``.
|
|
26
|
+
|
|
27
|
+
If not using ``SIER2_INI``, the ``sier2`` directory will be created
|
|
28
|
+
if it does not exist.
|
|
29
|
+
|
|
30
|
+
TODO don't create the sier2 directory until the ini file is written.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# If a config file has been explicitly set in an environment variable,
|
|
34
|
+
# use it.
|
|
35
|
+
#
|
|
36
|
+
ini = os.environ.get('SIER2_INI', None)
|
|
37
|
+
if ini:
|
|
38
|
+
return Path(ini)
|
|
39
|
+
|
|
16
40
|
if os.name=='nt':
|
|
41
|
+
# Windows.
|
|
42
|
+
#
|
|
17
43
|
prdir = Path(os.environ['APPDATA']) / 'sier2'
|
|
18
44
|
else:
|
|
45
|
+
# Linux.
|
|
46
|
+
#
|
|
19
47
|
prdir = os.environ.get('XDG_CONFIG_HOME', None)
|
|
20
48
|
if prdir:
|
|
21
49
|
prdir = Path(prdir)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from ._block import Block, BlockError, BlockValidateError, BlockState
|
|
2
2
|
from dataclasses import dataclass, field #, KW_ONLY, field
|
|
3
|
-
from collections import
|
|
4
|
-
import holoviews as hv
|
|
3
|
+
from collections import deque
|
|
5
4
|
from importlib.metadata import entry_points
|
|
6
5
|
import threading
|
|
7
6
|
import sys
|
|
@@ -578,75 +577,6 @@ class Dag:
|
|
|
578
577
|
'connections': connections
|
|
579
578
|
}
|
|
580
579
|
|
|
581
|
-
def hv_graph(self):
|
|
582
|
-
"""Build a HoloViews Graph to visualise the block connections."""
|
|
583
|
-
|
|
584
|
-
src: list[Block] = []
|
|
585
|
-
dst: list[Block] = []
|
|
586
|
-
|
|
587
|
-
def build_layers():
|
|
588
|
-
"""Traverse the block pairs and organise them into layers.
|
|
589
|
-
|
|
590
|
-
The first layer contains the root (no input) nodes.
|
|
591
|
-
"""
|
|
592
|
-
|
|
593
|
-
ranks = {}
|
|
594
|
-
remaining = self._block_pairs[:]
|
|
595
|
-
|
|
596
|
-
# Find the root nodes and assign them a layer.
|
|
597
|
-
#
|
|
598
|
-
src[:], dst[:] = zip(*remaining)
|
|
599
|
-
S = list(set([s for s in src if s not in dst]))
|
|
600
|
-
for s in S:
|
|
601
|
-
ranks[s.name] = 0
|
|
602
|
-
|
|
603
|
-
n_layers = 1
|
|
604
|
-
while remaining:
|
|
605
|
-
for s, d in remaining:
|
|
606
|
-
if s.name in ranks:
|
|
607
|
-
# This destination could be from sources at different layers.
|
|
608
|
-
# Make sure the deepest one is used.
|
|
609
|
-
#
|
|
610
|
-
ranks[d.name] = max(ranks.get(d.name, 0), ranks[s.name] + 1)
|
|
611
|
-
n_layers = max(n_layers, ranks[d.name])
|
|
612
|
-
|
|
613
|
-
remaining = [(s,d) for s,d in remaining if d.name not in ranks]
|
|
614
|
-
|
|
615
|
-
return n_layers, ranks
|
|
616
|
-
|
|
617
|
-
def layout(_):
|
|
618
|
-
"""Arrange the graph nodes."""
|
|
619
|
-
|
|
620
|
-
max_width = 0
|
|
621
|
-
|
|
622
|
-
# Arrange the graph y by layer from top to bottom.
|
|
623
|
-
# For x, for no we start at 0 and +1 in each layer.
|
|
624
|
-
#
|
|
625
|
-
yx = {y:0 for y in ranks.values()}
|
|
626
|
-
gxy = {}
|
|
627
|
-
for g, y in ranks.items():
|
|
628
|
-
gxy[g] = [yx[y], y]
|
|
629
|
-
yx[y] += 1
|
|
630
|
-
max_width = max(max_width, yx[y])
|
|
631
|
-
|
|
632
|
-
# Balance out the x in each layer.
|
|
633
|
-
#
|
|
634
|
-
for y in range(n_layers+1):
|
|
635
|
-
layer = {name: xy for name,xy in gxy.items() if xy[1]==y}
|
|
636
|
-
if len(layer)<max_width:
|
|
637
|
-
for x, (name, xy) in enumerate(layer.items(), 1):
|
|
638
|
-
gxy[name][0] = x/max_width
|
|
639
|
-
|
|
640
|
-
return gxy
|
|
641
|
-
|
|
642
|
-
n_layers, ranks = build_layers()
|
|
643
|
-
|
|
644
|
-
src_names = [g.name for g in src]
|
|
645
|
-
dst_names = [g.name for g in dst]
|
|
646
|
-
g = hv.Graph(((src_names, dst_names),))
|
|
647
|
-
|
|
648
|
-
return hv.element.graphs.layout_nodes(g, layout=layout)
|
|
649
|
-
|
|
650
580
|
def topological_sort(pairs):
|
|
651
581
|
"""Implement a topological sort as described at
|
|
652
582
|
`Topological sorting <https://en.wikipedia.org/wiki/Topological_sorting>`_.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Generate a dag chart.
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
from sier2 import Block, Dag
|
|
5
|
+
|
|
6
|
+
from bokeh.plotting import figure, show, ColumnDataSource, curdoc, output_notebook
|
|
7
|
+
from bokeh.models import HoverTool
|
|
8
|
+
|
|
9
|
+
from bokeh.resources import INLINE
|
|
10
|
+
output_notebook(resources=INLINE)
|
|
11
|
+
|
|
12
|
+
def bokeh_graph(dag: Dag):
|
|
13
|
+
"""Build a Bokeh figure to visualise the block connections."""
|
|
14
|
+
|
|
15
|
+
src: list[Block] = []
|
|
16
|
+
dst: list[Block] = []
|
|
17
|
+
|
|
18
|
+
def build_layers():
|
|
19
|
+
"""Traverse the block pairs and organise them into layers.
|
|
20
|
+
|
|
21
|
+
The first layer contains the root (no input) nodes.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
ranks = {}
|
|
25
|
+
remaining = dag._block_pairs[:]
|
|
26
|
+
|
|
27
|
+
# Find the root nodes and assign them a layer.
|
|
28
|
+
#
|
|
29
|
+
src[:], dst[:] = zip(*remaining)
|
|
30
|
+
S = list(set([s for s in src if s not in dst]))
|
|
31
|
+
for s in S:
|
|
32
|
+
ranks[s.name] = 0
|
|
33
|
+
|
|
34
|
+
n_layers = 1
|
|
35
|
+
while remaining:
|
|
36
|
+
for s, d in remaining:
|
|
37
|
+
if s.name in ranks:
|
|
38
|
+
# This destination could be from sources at different layers.
|
|
39
|
+
# Make sure the deepest one is used.
|
|
40
|
+
#
|
|
41
|
+
ranks[d.name] = max(ranks.get(d.name, 0), ranks[s.name] + 1)
|
|
42
|
+
n_layers = max(n_layers, ranks[d.name])
|
|
43
|
+
|
|
44
|
+
remaining = [(s,d) for s,d in remaining if d.name not in ranks]
|
|
45
|
+
|
|
46
|
+
return n_layers, ranks
|
|
47
|
+
|
|
48
|
+
def layout():
|
|
49
|
+
"""Arrange the graph nodes."""
|
|
50
|
+
|
|
51
|
+
max_width = 0
|
|
52
|
+
|
|
53
|
+
# Arrange the graph y by layer from top to bottom.
|
|
54
|
+
# For x, for now we start at 0 and +1 in each layer.
|
|
55
|
+
#
|
|
56
|
+
yx = {y:0 for y in ranks.values()}
|
|
57
|
+
gxy = {}
|
|
58
|
+
for g, y in ranks.items():
|
|
59
|
+
gxy[g] = [yx[y], y]
|
|
60
|
+
yx[y] += 1
|
|
61
|
+
max_width = max(max_width, yx[y])
|
|
62
|
+
|
|
63
|
+
# Balance out the x in each layer.
|
|
64
|
+
#
|
|
65
|
+
for y in range(n_layers+1):
|
|
66
|
+
layer = {name: xy for name,xy in gxy.items() if xy[1]==y}
|
|
67
|
+
if len(layer)<max_width:
|
|
68
|
+
for x, (name, xy) in enumerate(layer.items(), 1):
|
|
69
|
+
gxy[name][0] = x/max_width
|
|
70
|
+
|
|
71
|
+
return gxy
|
|
72
|
+
|
|
73
|
+
n_layers, ranks = build_layers()
|
|
74
|
+
|
|
75
|
+
ly = layout()
|
|
76
|
+
|
|
77
|
+
linexs = []
|
|
78
|
+
lineys = []
|
|
79
|
+
for s, d in dag._block_pairs:
|
|
80
|
+
print(s.name, d.name)
|
|
81
|
+
linexs.append((ly[s.name][0], ly[d.name][0]))
|
|
82
|
+
lineys.append((ly[s.name][1], ly[d.name][1]))
|
|
83
|
+
|
|
84
|
+
xs, ys = zip(*ly.values())
|
|
85
|
+
|
|
86
|
+
c_source = ColumnDataSource({
|
|
87
|
+
'xs': xs,
|
|
88
|
+
'ys': ys,
|
|
89
|
+
'names': list(ly.keys())
|
|
90
|
+
})
|
|
91
|
+
l_source = ColumnDataSource({
|
|
92
|
+
'linexs': linexs,
|
|
93
|
+
'lineys': lineys
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
curdoc().theme = 'dark_minimal'
|
|
97
|
+
p = figure(tools='pan,wheel_zoom,box_zoom,reset', height=300, width=300)
|
|
98
|
+
p.axis.visible = False
|
|
99
|
+
p.xgrid.visible = False
|
|
100
|
+
p.ygrid.visible = False
|
|
101
|
+
p.y_range.flipped = True # y-axis goes down instead of up.
|
|
102
|
+
l = p.multi_line(xs='linexs', ys='lineys', source=l_source)
|
|
103
|
+
c = p.circle(x='xs', y='ys', radius=0.05, line_color='black', fill_color='steelblue', hover_fill_color='#7f7f7f', source=c_source)
|
|
104
|
+
p.add_tools(HoverTool(tooltips=[('Block', '@names')], renderers=[c]))
|
|
105
|
+
|
|
106
|
+
return p
|
|
@@ -11,6 +11,7 @@ from .._dag import _InputValues
|
|
|
11
11
|
from .._util import trim
|
|
12
12
|
from ._feedlogger import getDagPanelLogger, getBlockPanelLogger
|
|
13
13
|
from ._panel_util import _get_state_color, dag_doc
|
|
14
|
+
from ._chart import bokeh_graph
|
|
14
15
|
|
|
15
16
|
NTHREADS = 2
|
|
16
17
|
|
|
@@ -247,13 +248,14 @@ def _prepare_to_show(dag: Dag):
|
|
|
247
248
|
card = pn.Card(pn.pane.Markdown(doc, sizing_mode='stretch_width'), header=pn.Row(name_text), sizing_mode='stretch_width')
|
|
248
249
|
cards.append(card)
|
|
249
250
|
|
|
250
|
-
cards.extend(BlockCard(parent_template=template, dag=dag, w=gw, dag_logger=dag_logger) for gw in dag.get_sorted())
|
|
251
|
+
cards.extend(BlockCard(parent_template=template, dag=dag, w=gw, dag_logger=dag_logger) for gw in dag.get_sorted() if gw.block_visible)
|
|
251
252
|
|
|
252
253
|
template.main.append(pn.Column(*cards))
|
|
253
254
|
template.sidebar.append(
|
|
254
255
|
pn.Column(
|
|
255
256
|
switch,
|
|
256
|
-
pn.panel(dag.hv_graph().opts(invert_yaxis=True, xaxis=None, yaxis=None)),
|
|
257
|
+
# pn.panel(dag.hv_graph().opts(invert_yaxis=True, xaxis=None, yaxis=None)),
|
|
258
|
+
pn.Row(pn.panel(bokeh_graph(dag)), max_width=400, max_height=200),
|
|
257
259
|
log_feed,
|
|
258
260
|
info_fp_holder
|
|
259
261
|
)
|
|
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
|