QuLab 2.10.10__cp313-cp313-win_amd64.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.
Files changed (107) hide show
  1. qulab/__init__.py +33 -0
  2. qulab/__main__.py +4 -0
  3. qulab/cli/__init__.py +0 -0
  4. qulab/cli/commands.py +30 -0
  5. qulab/cli/config.py +170 -0
  6. qulab/cli/decorators.py +28 -0
  7. qulab/dicttree.py +523 -0
  8. qulab/executor/__init__.py +5 -0
  9. qulab/executor/analyze.py +188 -0
  10. qulab/executor/cli.py +434 -0
  11. qulab/executor/load.py +563 -0
  12. qulab/executor/registry.py +185 -0
  13. qulab/executor/schedule.py +543 -0
  14. qulab/executor/storage.py +615 -0
  15. qulab/executor/template.py +259 -0
  16. qulab/executor/utils.py +194 -0
  17. qulab/expression.py +827 -0
  18. qulab/fun.cp313-win_amd64.pyd +0 -0
  19. qulab/monitor/__init__.py +1 -0
  20. qulab/monitor/__main__.py +8 -0
  21. qulab/monitor/config.py +41 -0
  22. qulab/monitor/dataset.py +77 -0
  23. qulab/monitor/event_queue.py +54 -0
  24. qulab/monitor/mainwindow.py +234 -0
  25. qulab/monitor/monitor.py +115 -0
  26. qulab/monitor/ploter.py +123 -0
  27. qulab/monitor/qt_compat.py +16 -0
  28. qulab/monitor/toolbar.py +265 -0
  29. qulab/scan/__init__.py +2 -0
  30. qulab/scan/curd.py +221 -0
  31. qulab/scan/models.py +554 -0
  32. qulab/scan/optimize.py +76 -0
  33. qulab/scan/query.py +387 -0
  34. qulab/scan/record.py +603 -0
  35. qulab/scan/scan.py +1166 -0
  36. qulab/scan/server.py +450 -0
  37. qulab/scan/space.py +213 -0
  38. qulab/scan/utils.py +234 -0
  39. qulab/storage/__init__.py +0 -0
  40. qulab/storage/__main__.py +51 -0
  41. qulab/storage/backend/__init__.py +0 -0
  42. qulab/storage/backend/redis.py +204 -0
  43. qulab/storage/base_dataset.py +352 -0
  44. qulab/storage/chunk.py +60 -0
  45. qulab/storage/dataset.py +127 -0
  46. qulab/storage/file.py +273 -0
  47. qulab/storage/models/__init__.py +22 -0
  48. qulab/storage/models/base.py +4 -0
  49. qulab/storage/models/config.py +28 -0
  50. qulab/storage/models/file.py +89 -0
  51. qulab/storage/models/ipy.py +58 -0
  52. qulab/storage/models/models.py +88 -0
  53. qulab/storage/models/record.py +161 -0
  54. qulab/storage/models/report.py +22 -0
  55. qulab/storage/models/tag.py +93 -0
  56. qulab/storage/storage.py +95 -0
  57. qulab/sys/__init__.py +2 -0
  58. qulab/sys/chat.py +688 -0
  59. qulab/sys/device/__init__.py +3 -0
  60. qulab/sys/device/basedevice.py +255 -0
  61. qulab/sys/device/loader.py +86 -0
  62. qulab/sys/device/utils.py +79 -0
  63. qulab/sys/drivers/FakeInstrument.py +68 -0
  64. qulab/sys/drivers/__init__.py +0 -0
  65. qulab/sys/ipy_events.py +125 -0
  66. qulab/sys/net/__init__.py +0 -0
  67. qulab/sys/net/bencoder.py +205 -0
  68. qulab/sys/net/cli.py +169 -0
  69. qulab/sys/net/dhcp.py +543 -0
  70. qulab/sys/net/dhcpd.py +176 -0
  71. qulab/sys/net/kad.py +1142 -0
  72. qulab/sys/net/kcp.py +192 -0
  73. qulab/sys/net/nginx.py +194 -0
  74. qulab/sys/progress.py +190 -0
  75. qulab/sys/rpc/__init__.py +0 -0
  76. qulab/sys/rpc/client.py +0 -0
  77. qulab/sys/rpc/exceptions.py +96 -0
  78. qulab/sys/rpc/msgpack.py +1052 -0
  79. qulab/sys/rpc/msgpack.pyi +41 -0
  80. qulab/sys/rpc/router.py +35 -0
  81. qulab/sys/rpc/rpc.py +412 -0
  82. qulab/sys/rpc/serialize.py +139 -0
  83. qulab/sys/rpc/server.py +29 -0
  84. qulab/sys/rpc/socket.py +29 -0
  85. qulab/sys/rpc/utils.py +25 -0
  86. qulab/sys/rpc/worker.py +0 -0
  87. qulab/sys/rpc/zmq_socket.py +227 -0
  88. qulab/tools/__init__.py +0 -0
  89. qulab/tools/connection_helper.py +39 -0
  90. qulab/typing.py +2 -0
  91. qulab/utils.py +95 -0
  92. qulab/version.py +1 -0
  93. qulab/visualization/__init__.py +188 -0
  94. qulab/visualization/__main__.py +71 -0
  95. qulab/visualization/_autoplot.py +464 -0
  96. qulab/visualization/plot_circ.py +319 -0
  97. qulab/visualization/plot_layout.py +408 -0
  98. qulab/visualization/plot_seq.py +242 -0
  99. qulab/visualization/qdat.py +152 -0
  100. qulab/visualization/rot3d.py +23 -0
  101. qulab/visualization/widgets.py +86 -0
  102. qulab-2.10.10.dist-info/METADATA +110 -0
  103. qulab-2.10.10.dist-info/RECORD +107 -0
  104. qulab-2.10.10.dist-info/WHEEL +5 -0
  105. qulab-2.10.10.dist-info/entry_points.txt +2 -0
  106. qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
  107. qulab-2.10.10.dist-info/top_level.txt +1 -0
qulab/executor/load.py ADDED
@@ -0,0 +1,563 @@
1
+ import atexit
2
+ import inspect
3
+ import os
4
+ import pickle
5
+ import shutil
6
+ import tempfile
7
+ import time
8
+ import warnings
9
+ from importlib.util import module_from_spec, spec_from_file_location
10
+ from pathlib import Path
11
+ from types import ModuleType
12
+ from typing import Any
13
+
14
+ from loguru import logger
15
+
16
+ from .registry import Registry
17
+ from .storage import Report
18
+ from .template import (TemplateKeyError, TemplateTypeError, decode_mapping,
19
+ inject_mapping)
20
+
21
+
22
+ class SetConfigWorkflow():
23
+ __timeout__ = None
24
+ __mtime__ = 0
25
+ __source__ = ''
26
+
27
+ def __init__(self, key):
28
+ self.key = key
29
+ self.__workflow_id__ = f"cfg:{self.key}"
30
+
31
+ def depends(self):
32
+ return []
33
+
34
+ def check_state(self, history: Report) -> bool:
35
+ reg = Registry()
36
+ try:
37
+ return self._equal(history.parameters[self.key], reg.get(self.key))
38
+ except:
39
+ return False
40
+
41
+ def calibrate(self):
42
+ reg = Registry()
43
+ try:
44
+ value = reg.get(self.key)
45
+ except:
46
+ value = eval(input(f'"{self.key}": '))
47
+ return value
48
+
49
+ def analyze(self, report: Report, history: Report):
50
+ report.state = 'OK'
51
+ report.parameters = {self.key: report.data}
52
+ return report
53
+
54
+ def check(self):
55
+ return self.calibrate()
56
+
57
+ def check_analyze(self, report: Report, history: Report | None):
58
+ if self.check_state(history):
59
+ report.state = 'OK'
60
+ report.parameters = {self.key: history.data}
61
+ else:
62
+ report.state = 'Outdated'
63
+ return report
64
+
65
+ @staticmethod
66
+ def _equal(a, b):
67
+ import numpy as np
68
+
69
+ if a is b:
70
+ return True
71
+ try:
72
+ return a == b
73
+ except:
74
+ pass
75
+
76
+ if isinstance(a, np.ndarray) and isinstance(b, np.ndarray):
77
+ return a.shape == b.shape and np.all(a == b)
78
+
79
+ return False
80
+
81
+ def __hash__(self):
82
+ return hash(self.__workflow_id__)
83
+
84
+
85
+ WorkflowType = ModuleType | SetConfigWorkflow
86
+
87
+
88
+ def can_call_without_args(func):
89
+ if not callable(func):
90
+ return False
91
+
92
+ # 获取函数签名
93
+ sig = inspect.signature(func)
94
+ for param in sig.parameters.values():
95
+ # 如果有参数没有默认值且不是可变参数,则无法无参调用
96
+ if (param.default is param.empty and param.kind
97
+ not in (param.VAR_POSITIONAL, param.VAR_KEYWORD)):
98
+ return False
99
+ return True
100
+
101
+
102
+ def verify_calibrate_method(module: WorkflowType):
103
+ if not hasattr(module, 'calibrate'):
104
+ raise AttributeError(
105
+ f"Workflow {module.__file__} does not have 'calibrate' function")
106
+
107
+ if not can_call_without_args(module.calibrate):
108
+ raise AttributeError(
109
+ f"Workflow {module.__file__} 'calibrate' function should not have any parameters"
110
+ )
111
+
112
+ if not hasattr(module, 'analyze'):
113
+ raise AttributeError(
114
+ f"Workflow {module.__file__} does not have 'analyze' function")
115
+
116
+
117
+ def verify_check_method(module: WorkflowType):
118
+ if not hasattr(module, 'check'):
119
+ warnings.warn(
120
+ f"Workflow {module.__file__} does not have 'check' function, it will be set to 'calibrate' function"
121
+ )
122
+ else:
123
+ if not can_call_without_args(module.check):
124
+ raise AttributeError(
125
+ f"Workflow {module.__file__} 'check' function should not have any parameters"
126
+ )
127
+
128
+ if not hasattr(module, 'check_analyze'):
129
+ raise AttributeError(
130
+ f"Workflow {module.__file__} has 'check' function but does not have 'check_analyze' function"
131
+ )
132
+
133
+
134
+ def verify_dependence_key(workflow: str | tuple[str, dict[str, Any]]
135
+ | tuple[str, str, dict[str, Any]], base_path: Path):
136
+ if isinstance(workflow, str):
137
+ return
138
+ if not isinstance(workflow, tuple) or len(workflow) not in [2, 3]:
139
+ raise ValueError(f"Invalid workflow: {workflow}")
140
+
141
+ if len(workflow) == 2 and isinstance(workflow[1], str):
142
+ file_name, mapping = workflow
143
+ if not Path(file_name).exists():
144
+ raise FileNotFoundError(f"File not found: {file_name}")
145
+ elif len(workflow) == 2 and isinstance(workflow[1], dict):
146
+ template_path, mapping = workflow
147
+ if not (Path(base_path) / template_path).exists():
148
+ raise FileNotFoundError(f"File not found: {template_path}")
149
+ elif len(workflow) == 3:
150
+ template_path, target_path, mapping = workflow
151
+ if not (Path(base_path) / template_path).exists():
152
+ raise FileNotFoundError(f"File not found: {template_path}")
153
+ if not isinstance(target_path, (Path, str)) or target_path == '':
154
+ raise ValueError(f"Invalid target_path: {target_path}")
155
+ if not isinstance(target_path, (Path, str)):
156
+ raise ValueError(f"Invalid target_path: {target_path}")
157
+ if Path(target_path).suffix != '.py':
158
+ raise ValueError(
159
+ f"Invalid target_path: {target_path}. Only .py file is supported"
160
+ )
161
+ else:
162
+ raise ValueError(f"Invalid workflow: {workflow}")
163
+
164
+ if not isinstance(mapping, dict):
165
+ raise ValueError(f"Invalid mapping: {mapping}")
166
+
167
+ for key, value in mapping.items():
168
+ if not isinstance(key, str):
169
+ raise ValueError(
170
+ f"Invalid key: {key}, should be str type and valid identifier")
171
+ if not key.isidentifier():
172
+ raise ValueError(f"Invalid key: {key}, should be identifier")
173
+ try:
174
+ pickle.dumps(value)
175
+ except Exception as e:
176
+ raise ValueError(
177
+ f"Invalid value: {key}: {value}, should be pickleable") from e
178
+ return
179
+
180
+
181
+ def verify_depends(module: WorkflowType, base_path):
182
+ if not hasattr(module, 'depends'):
183
+ return
184
+
185
+ deps = []
186
+
187
+ if callable(module.depends):
188
+ if not can_call_without_args(module.depends):
189
+ raise AttributeError(
190
+ f"Workflow {module.__file__} 'depends' function should not have any parameters"
191
+ )
192
+ deps = list(module.depends())
193
+ elif isinstance(module.depends, (list, tuple)):
194
+ deps = module.depends
195
+ else:
196
+ raise AttributeError(
197
+ f"Workflow {module.__file__} 'depends' should be a callable or a list"
198
+ )
199
+ for workflow in deps:
200
+ verify_dependence_key(workflow, base_path)
201
+
202
+
203
+ def verify_entries(module: WorkflowType, base_path):
204
+ if not hasattr(module, 'entries'):
205
+ return
206
+
207
+ deps = []
208
+
209
+ if callable(module.entries):
210
+ if not can_call_without_args(module.entries):
211
+ raise AttributeError(
212
+ f"Workflow {module.__file__} 'entries' function should not have any parameters"
213
+ )
214
+ deps = list(module.entries())
215
+ elif isinstance(module.entries, (list, tuple)):
216
+ deps = module.entries
217
+ else:
218
+ raise AttributeError(
219
+ f"Workflow {module.__file__} 'entries' should be a callable or a list"
220
+ )
221
+ for workflow in deps:
222
+ verify_dependence_key(workflow, base_path)
223
+
224
+
225
+ def is_workflow(module: ModuleType) -> bool:
226
+ try:
227
+ verify_calibrate_method(module)
228
+ return True
229
+ except AttributeError:
230
+ return False
231
+
232
+
233
+ def is_template(path: str | Path) -> bool:
234
+ path = Path(path)
235
+ if path.name == 'template.py':
236
+ return True
237
+ if path.name.endswith('_template.py'):
238
+ return True
239
+ if 'templates' in path.parts:
240
+ return True
241
+ return False
242
+
243
+
244
+ def find_unreferenced_workflows(path: str) -> list[str]:
245
+ root = Path(path).resolve()
246
+ workflows = []
247
+ workflow_paths: set[str] = set()
248
+
249
+ # Collect all workflow modules
250
+ for file_path in root.rglob("*.py"):
251
+ if file_path.name == "__init__.py":
252
+ continue
253
+ if is_template(file_path):
254
+ continue
255
+ try:
256
+ rel_path = file_path.relative_to(root)
257
+ except ValueError:
258
+ continue
259
+
260
+ module = load_workflow_from_file(str(rel_path), root)
261
+
262
+ if is_workflow(module):
263
+ rel_str = str(rel_path)
264
+ workflows.append(rel_str)
265
+ workflow_paths.add(rel_str)
266
+
267
+ dependencies: set[str] = set()
268
+
269
+ # Check dependencies for each workflow module
270
+ for rel_str in workflows:
271
+ module = load_workflow_from_file(rel_str, root)
272
+
273
+ depends_func = getattr(module, "depends", None)
274
+ if depends_func and callable(depends_func):
275
+ if not can_call_without_args(depends_func):
276
+ warnings.warn(
277
+ f"Skipping depends() in {rel_str} as it requires arguments"
278
+ )
279
+ continue
280
+ try:
281
+ depends_list = [
282
+ n.__workflow_id__ for n in get_dependents(module, root)
283
+ ]
284
+ except Exception as e:
285
+ warnings.warn(f"Error calling depends() in {rel_str}: {e}")
286
+ continue
287
+
288
+ if not isinstance(depends_list, list) or not all(
289
+ isinstance(item, str) for item in depends_list):
290
+ warnings.warn(
291
+ f"depends() in {rel_str} did not return a list of strings")
292
+ continue
293
+
294
+ for dep in depends_list:
295
+ dep_full = (root / dep).resolve()
296
+ try:
297
+ dep_rel = dep_full.relative_to(root)
298
+ except ValueError:
299
+ continue
300
+ dep_rel_str = str(dep_rel)
301
+ if dep_rel_str in workflow_paths:
302
+ dependencies.add(dep_rel_str)
303
+
304
+ # Determine unreferenced workflows
305
+ unreferenced = [wp for wp in workflows if wp not in dependencies]
306
+ return unreferenced
307
+
308
+
309
+ def load_workflow_from_file(file_name: str,
310
+ base_path: str | Path,
311
+ package='workflows',
312
+ veryfy_source_code: bool = True) -> WorkflowType:
313
+ base_path = Path(base_path)
314
+ path = Path(file_name)
315
+ if not (base_path / path).exists():
316
+ raise FileNotFoundError(f"File not found: {base_path / path}")
317
+ module_name = f"{package}.{'.'.join([*path.parts[:-1], path.stem])}"
318
+ spec = spec_from_file_location(module_name, base_path / path)
319
+ module = module_from_spec(spec)
320
+ spec.loader.exec_module(module)
321
+ module.__mtime__ = (base_path / path).stat().st_mtime
322
+ source_code = (base_path / path).read_text()
323
+ module.__source__ = source_code
324
+
325
+ if hasattr(module, 'entries'):
326
+ if veryfy_source_code:
327
+ verify_entries(module, base_path)
328
+ return module
329
+
330
+ if not hasattr(module, '__timeout__'):
331
+ module.__timeout__ = None
332
+
333
+ if not hasattr(module, 'depends'):
334
+ module.depends = lambda: []
335
+
336
+ if veryfy_source_code:
337
+ verify_depends(module, base_path)
338
+ verify_calibrate_method(module)
339
+ verify_check_method(module)
340
+
341
+ return module
342
+
343
+
344
+ def load_workflow_from_source_code(workflow_id: str,
345
+ source_code: str,
346
+ package='workflows') -> WorkflowType:
347
+ workflow_path = Path(workflow_id)
348
+ module_name = f"{package}.{'.'.join([*workflow_path.parts[:-1], workflow_path.stem])}"
349
+
350
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.py').name
351
+ with open(temp_file, 'w') as f:
352
+ f.write(source_code)
353
+
354
+ atexit.register(lambda f=temp_file: Path(f).unlink(missing_ok=True))
355
+
356
+ spec = spec_from_file_location(module_name, temp_file)
357
+ module = module_from_spec(spec)
358
+ spec.loader.exec_module(module)
359
+
360
+ module.__source__ = source_code
361
+ module.__mtime__ = 0
362
+ module.__workflow_id__ = workflow_id
363
+
364
+ if not hasattr(module, '__timeout__'):
365
+ module.__timeout__ = None
366
+
367
+ if not hasattr(module, 'depends'):
368
+ module.depends = lambda: []
369
+
370
+ return module
371
+
372
+
373
+ def _generate_target_file_path(template_path: str | Path, hash_str: str,
374
+ content: str, base_path: str | Path) -> Path:
375
+ path = Path(template_path)
376
+ if path.stem == 'template':
377
+ path = path.parent / f'tmp{hash_str}.py'
378
+ elif path.stem.endswith('_template'):
379
+ path = path.parent / path.stem.replace('_template',
380
+ f'_tmp{hash_str}.py')
381
+ else:
382
+ path = path.parent / f'{path.stem}_tmp{hash_str}.py'
383
+
384
+ if 'templates' in path.parts:
385
+ return Path(*['run' if p == 'templates' else p for p in path.parts])
386
+ else:
387
+ return Path('run') / path
388
+
389
+
390
+ def load_workflow_from_template(
391
+ template_path: str,
392
+ mapping: dict[str, str],
393
+ base_path: str | Path,
394
+ target_path: str | None = None,
395
+ package='workflows',
396
+ mtime: float = 0,
397
+ veryfy_source_code: bool = True) -> WorkflowType:
398
+ base_path = Path(base_path)
399
+ path = Path(template_path)
400
+
401
+ with open(base_path / path) as f:
402
+ template = f.read()
403
+
404
+ mtime = max((base_path / template_path).stat().st_mtime, mtime)
405
+
406
+ content, hash_str = inject_mapping(template, mapping, str(path))
407
+
408
+ if target_path is None:
409
+ path = _generate_target_file_path(template_path, hash_str, content,
410
+ base_path)
411
+ else:
412
+ path = target_path
413
+
414
+ file = base_path / path
415
+ if not file.exists() or (file.stat().st_mtime < mtime
416
+ and file.read_text() != content):
417
+ file.parent.mkdir(parents=True, exist_ok=True)
418
+ with open(file, 'w') as f:
419
+ f.write(content)
420
+ elif file.read_text() != content:
421
+ logger.warning(
422
+ f"`{file}` already exists and is different from the new one generated from template `{template_path}`"
423
+ )
424
+
425
+ module = load_workflow_from_file(str(path), base_path, package,
426
+ veryfy_source_code)
427
+ module.__mtime__ = max(mtime, module.__mtime__)
428
+ if module.__source__ == content:
429
+ module.__source__ = template, mapping, str(template_path)
430
+
431
+ return module
432
+
433
+
434
+ def load_workflow(workflow: str | tuple[str, dict],
435
+ base_path: str | Path,
436
+ package='workflows',
437
+ mtime: float = 0,
438
+ inject: dict | None = None,
439
+ veryfy_source_code: bool = True) -> WorkflowType:
440
+ if isinstance(workflow, tuple):
441
+ if len(workflow) == 2:
442
+ file_name, mapping = workflow
443
+ if inject is None:
444
+ w = load_workflow_from_template(file_name, mapping, base_path,
445
+ None, package, mtime,
446
+ veryfy_source_code)
447
+ else:
448
+ w = load_workflow_from_template(file_name, inject, base_path,
449
+ None, package, mtime,
450
+ veryfy_source_code)
451
+ elif len(workflow) == 3:
452
+ template_path, target_path, mapping = workflow
453
+ if inject is None:
454
+ w = load_workflow_from_template(template_path, mapping,
455
+ base_path, target_path,
456
+ package, mtime,
457
+ veryfy_source_code)
458
+ else:
459
+ w = load_workflow_from_template(template_path, inject,
460
+ base_path, target_path,
461
+ package, mtime,
462
+ veryfy_source_code)
463
+ else:
464
+ raise ValueError(f"Invalid workflow: {workflow}")
465
+ w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
466
+ elif isinstance(workflow, str):
467
+ if workflow.startswith('cfg:'):
468
+ key = workflow[4:]
469
+ w = SetConfigWorkflow(key)
470
+ w.__workflow_id__ = workflow
471
+ else:
472
+ w = load_workflow_from_file(workflow, base_path, package,
473
+ veryfy_source_code)
474
+ w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
475
+ else:
476
+ raise TypeError(f"Invalid workflow: {workflow}")
477
+
478
+ return w
479
+
480
+
481
+ def _load_workflow_list(workflow, lst, code_path, veryfy_source_code):
482
+ ret = []
483
+ for i, n in enumerate(lst):
484
+ try:
485
+ ret.append(
486
+ load_workflow(n,
487
+ code_path,
488
+ mtime=workflow.__mtime__,
489
+ veryfy_source_code=veryfy_source_code))
490
+ except TemplateKeyError as e:
491
+ msg, *_ = e.args
492
+ e.args = (
493
+ f"Workflow {workflow.__workflow_id__} entry {i}: {msg}", )
494
+ raise e
495
+ return ret
496
+
497
+
498
+ def get_dependents(workflow: WorkflowType, code_path: str | Path,
499
+ veryfy_source_code: bool) -> list[WorkflowType]:
500
+ if callable(getattr(workflow, 'depends', None)):
501
+ if not can_call_without_args(workflow.depends):
502
+ raise AttributeError(
503
+ f'Workflow {workflow.__workflow_id__} "depends" function should not have any parameters'
504
+ )
505
+ return _load_workflow_list(workflow, workflow.depends(), code_path,
506
+ veryfy_source_code)
507
+ elif isinstance(getattr(workflow, 'depends', None), (list, tuple)):
508
+ return _load_workflow_list(workflow, workflow.depends, code_path,
509
+ veryfy_source_code)
510
+ elif getattr(workflow, 'depends', None) is None:
511
+ return []
512
+ else:
513
+ raise AttributeError(
514
+ f'Workflow {workflow.__workflow_id__} "depends" should be a callable or a list'
515
+ )
516
+
517
+
518
+ def get_entries(workflow: WorkflowType, code_path: str | Path,
519
+ veryfy_source_code: bool) -> list[WorkflowType]:
520
+ if callable(getattr(workflow, 'entries', None)):
521
+ if not can_call_without_args(workflow.entries):
522
+ raise AttributeError(
523
+ f'Workflow {workflow.__workflow_id__} "entries" function should not have any parameters'
524
+ )
525
+ return _load_workflow_list(workflow, workflow.entries(), code_path,
526
+ veryfy_source_code)
527
+ elif isinstance(getattr(workflow, 'entries', None), (list, tuple)):
528
+ return _load_workflow_list(workflow, workflow.entries, code_path,
529
+ veryfy_source_code)
530
+ elif getattr(workflow, 'entries', None) is None:
531
+ return []
532
+ else:
533
+ raise AttributeError(
534
+ f'Workflow {workflow.__workflow_id__} "entries" should be a callable or a list'
535
+ )
536
+
537
+
538
+ def make_graph(workflow: WorkflowType,
539
+ graph: dict,
540
+ code_path: str | Path,
541
+ veryfy_source_code: bool = True):
542
+ if workflow.__workflow_id__ in graph:
543
+ return graph
544
+ graph[workflow.__workflow_id__] = []
545
+
546
+ if hasattr(workflow, 'entries'):
547
+ for w in get_entries(workflow, code_path, veryfy_source_code):
548
+ graph[workflow.__workflow_id__].append(w.__workflow_id__)
549
+ make_graph(w,
550
+ graph=graph,
551
+ code_path=code_path,
552
+ veryfy_source_code=veryfy_source_code)
553
+ elif hasattr(workflow, 'depends'):
554
+ for w in get_dependents(workflow, code_path, veryfy_source_code):
555
+ graph[workflow.__workflow_id__].append(w.__workflow_id__)
556
+ make_graph(w,
557
+ graph=graph,
558
+ code_path=code_path,
559
+ veryfy_source_code=veryfy_source_code)
560
+ if graph[workflow.__workflow_id__] == []:
561
+ del graph[workflow.__workflow_id__]
562
+
563
+ return graph