siliconcompiler 0.33.2__py3-none-any.whl → 0.34.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.
- siliconcompiler/__init__.py +2 -0
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/_common.py +1 -1
- siliconcompiler/apps/sc.py +1 -1
- siliconcompiler/apps/sc_issue.py +6 -4
- siliconcompiler/apps/sc_remote.py +3 -20
- siliconcompiler/apps/sc_show.py +2 -2
- siliconcompiler/apps/utils/replay.py +4 -4
- siliconcompiler/checklist.py +202 -1
- siliconcompiler/core.py +62 -293
- siliconcompiler/data/templates/email/general.j2 +3 -3
- siliconcompiler/data/templates/email/summary.j2 +1 -1
- siliconcompiler/data/templates/issue/README.txt +1 -1
- siliconcompiler/data/templates/report/sc_report.j2 +7 -7
- siliconcompiler/dependencyschema.py +392 -0
- siliconcompiler/design.py +758 -0
- siliconcompiler/flowgraph.py +79 -13
- siliconcompiler/optimizer/vizier.py +2 -2
- siliconcompiler/package/__init__.py +383 -223
- siliconcompiler/package/git.py +75 -77
- siliconcompiler/package/github.py +70 -97
- siliconcompiler/package/https.py +77 -93
- siliconcompiler/packageschema.py +260 -0
- siliconcompiler/pdk.py +5 -5
- siliconcompiler/remote/client.py +33 -15
- siliconcompiler/remote/server.py +2 -2
- siliconcompiler/report/dashboard/cli/__init__.py +6 -6
- siliconcompiler/report/dashboard/cli/board.py +4 -4
- siliconcompiler/report/dashboard/web/components/__init__.py +5 -5
- siliconcompiler/report/dashboard/web/components/flowgraph.py +4 -4
- siliconcompiler/report/dashboard/web/components/graph.py +2 -2
- siliconcompiler/report/dashboard/web/state.py +1 -1
- siliconcompiler/report/dashboard/web/utils/__init__.py +5 -5
- siliconcompiler/report/html_report.py +1 -1
- siliconcompiler/report/report.py +4 -4
- siliconcompiler/report/summary_table.py +2 -2
- siliconcompiler/report/utils.py +5 -5
- siliconcompiler/scheduler/__init__.py +3 -1382
- siliconcompiler/scheduler/docker.py +263 -0
- siliconcompiler/scheduler/run_node.py +10 -21
- siliconcompiler/scheduler/scheduler.py +311 -0
- siliconcompiler/scheduler/schedulernode.py +944 -0
- siliconcompiler/scheduler/send_messages.py +3 -3
- siliconcompiler/scheduler/slurm.py +149 -163
- siliconcompiler/scheduler/taskscheduler.py +45 -57
- siliconcompiler/schema/__init__.py +3 -3
- siliconcompiler/schema/baseschema.py +234 -11
- siliconcompiler/schema/editableschema.py +4 -0
- siliconcompiler/schema/journal.py +210 -0
- siliconcompiler/schema/namedschema.py +55 -2
- siliconcompiler/schema/parameter.py +14 -1
- siliconcompiler/schema/parametervalue.py +1 -34
- siliconcompiler/schema/schema_cfg.py +210 -349
- siliconcompiler/tool.py +412 -148
- siliconcompiler/tools/__init__.py +2 -0
- siliconcompiler/tools/builtin/_common.py +5 -5
- siliconcompiler/tools/builtin/concatenate.py +7 -7
- siliconcompiler/tools/builtin/minimum.py +4 -4
- siliconcompiler/tools/builtin/mux.py +4 -4
- siliconcompiler/tools/builtin/nop.py +4 -4
- siliconcompiler/tools/builtin/verify.py +8 -9
- siliconcompiler/tools/execute/exec_input.py +1 -1
- siliconcompiler/tools/genfasm/genfasm.py +1 -6
- siliconcompiler/tools/openroad/_apr.py +5 -1
- siliconcompiler/tools/openroad/antenna_repair.py +1 -1
- siliconcompiler/tools/openroad/macro_placement.py +1 -1
- siliconcompiler/tools/openroad/power_grid.py +1 -1
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +32 -25
- siliconcompiler/tools/opensta/timing.py +26 -3
- siliconcompiler/tools/slang/__init__.py +2 -2
- siliconcompiler/tools/surfer/__init__.py +0 -0
- siliconcompiler/tools/surfer/show.py +53 -0
- siliconcompiler/tools/surfer/surfer.py +30 -0
- siliconcompiler/tools/vpr/route.py +82 -0
- siliconcompiler/tools/vpr/vpr.py +23 -6
- siliconcompiler/tools/yosys/__init__.py +1 -1
- siliconcompiler/tools/yosys/scripts/procs.tcl +143 -0
- siliconcompiler/tools/yosys/{sc_synth_asic.tcl → scripts/sc_synth_asic.tcl} +4 -0
- siliconcompiler/tools/yosys/{sc_synth_fpga.tcl → scripts/sc_synth_fpga.tcl} +24 -77
- siliconcompiler/tools/yosys/syn_fpga.py +14 -0
- siliconcompiler/toolscripts/_tools.json +9 -13
- siliconcompiler/toolscripts/rhel9/install-vpr.sh +0 -2
- siliconcompiler/toolscripts/ubuntu22/install-surfer.sh +33 -0
- siliconcompiler/toolscripts/ubuntu24/install-surfer.sh +33 -0
- siliconcompiler/utils/__init__.py +4 -24
- siliconcompiler/utils/flowgraph.py +29 -28
- siliconcompiler/utils/issue.py +23 -29
- siliconcompiler/utils/logging.py +37 -7
- siliconcompiler/utils/showtools.py +6 -1
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/METADATA +16 -25
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/RECORD +98 -91
- siliconcompiler/scheduler/docker_runner.py +0 -254
- siliconcompiler/schema/journalingschema.py +0 -242
- siliconcompiler/tools/yosys/procs.tcl +0 -71
- siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +0 -68
- siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +0 -68
- siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +0 -68
- /siliconcompiler/tools/yosys/{sc_lec.tcl → scripts/sc_lec.tcl} +0 -0
- /siliconcompiler/tools/yosys/{sc_screenshot.tcl → scripts/sc_screenshot.tcl} +0 -0
- /siliconcompiler/tools/yosys/{syn_strategies.tcl → scripts/syn_strategies.tcl} +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/top_level.txt +0 -0
|
@@ -22,6 +22,7 @@ except ModuleNotFoundError:
|
|
|
22
22
|
import os.path
|
|
23
23
|
|
|
24
24
|
from .parameter import Parameter
|
|
25
|
+
from .journal import Journal
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class BaseSchema:
|
|
@@ -31,9 +32,10 @@ class BaseSchema:
|
|
|
31
32
|
'''
|
|
32
33
|
|
|
33
34
|
def __init__(self):
|
|
34
|
-
# Data storage for the schema
|
|
35
35
|
self.__manifest = {}
|
|
36
36
|
self.__default = None
|
|
37
|
+
self.__journal = Journal()
|
|
38
|
+
self.__parent = self
|
|
37
39
|
|
|
38
40
|
def _from_dict(self, manifest, keypath, version=None):
|
|
39
41
|
'''
|
|
@@ -48,6 +50,9 @@ class BaseSchema:
|
|
|
48
50
|
handled = set()
|
|
49
51
|
missing = set()
|
|
50
52
|
|
|
53
|
+
if "__journal__" in manifest:
|
|
54
|
+
self.__journal.from_dict(manifest["__journal__"])
|
|
55
|
+
|
|
51
56
|
if self.__default:
|
|
52
57
|
data = manifest.get("default", None)
|
|
53
58
|
if data:
|
|
@@ -74,16 +79,17 @@ class BaseSchema:
|
|
|
74
79
|
'''
|
|
75
80
|
Create a new schema based on the provided source files.
|
|
76
81
|
|
|
77
|
-
The two arguments to this
|
|
82
|
+
The two arguments to this method are mutually exclusive.
|
|
78
83
|
|
|
79
84
|
Args:
|
|
80
85
|
filepath (path): Initial manifest.
|
|
81
86
|
cfg (dict): Initial configuration dictionary.
|
|
82
87
|
'''
|
|
83
88
|
|
|
84
|
-
schema = cls()
|
|
85
89
|
if not filepath and cfg is None:
|
|
86
90
|
raise RuntimeError("filepath or dictionary is required")
|
|
91
|
+
|
|
92
|
+
schema = cls()
|
|
87
93
|
if filepath:
|
|
88
94
|
schema.read_manifest(filepath)
|
|
89
95
|
if cfg:
|
|
@@ -220,6 +226,8 @@ class BaseSchema:
|
|
|
220
226
|
if field == 'schema':
|
|
221
227
|
if isinstance(param, Parameter):
|
|
222
228
|
raise ValueError(f"[{','.join(keypath)}] is a complete keypath")
|
|
229
|
+
self.__journal.record("get", keypath, field=field, step=step, index=index)
|
|
230
|
+
param.__journal = self.__journal.get_child(*keypath)
|
|
223
231
|
return param
|
|
224
232
|
except KeyError:
|
|
225
233
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
@@ -227,7 +235,9 @@ class BaseSchema:
|
|
|
227
235
|
return param
|
|
228
236
|
|
|
229
237
|
try:
|
|
230
|
-
|
|
238
|
+
get_ret = param.get(field, step=step, index=index)
|
|
239
|
+
self.__journal.record("get", keypath, field=field, step=step, index=index)
|
|
240
|
+
return get_ret
|
|
231
241
|
except Exception as e:
|
|
232
242
|
new_msg = f"error while accessing [{','.join(keypath)}]: {e.args[0]}"
|
|
233
243
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -266,7 +276,12 @@ class BaseSchema:
|
|
|
266
276
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
267
277
|
|
|
268
278
|
try:
|
|
269
|
-
|
|
279
|
+
set_ret = param.set(value, field=field, clobber=clobber,
|
|
280
|
+
step=step, index=index)
|
|
281
|
+
if set_ret:
|
|
282
|
+
self.__journal.record("set", keypath, value=value, field=field,
|
|
283
|
+
step=step, index=index)
|
|
284
|
+
return set_ret
|
|
270
285
|
except Exception as e:
|
|
271
286
|
new_msg = f"error while setting [{','.join(keypath)}]: {e.args[0]}"
|
|
272
287
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -304,7 +319,11 @@ class BaseSchema:
|
|
|
304
319
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
305
320
|
|
|
306
321
|
try:
|
|
307
|
-
|
|
322
|
+
add_ret = param.add(value, field=field, step=step, index=index)
|
|
323
|
+
if add_ret:
|
|
324
|
+
self.__journal.record("add", keypath, value=value, field=field,
|
|
325
|
+
step=step, index=index)
|
|
326
|
+
return add_ret
|
|
308
327
|
except Exception as e:
|
|
309
328
|
new_msg = f"error while adding to [{','.join(keypath)}]: {e.args[0]}"
|
|
310
329
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -343,6 +362,7 @@ class BaseSchema:
|
|
|
343
362
|
|
|
344
363
|
try:
|
|
345
364
|
param.unset(step=step, index=index)
|
|
365
|
+
self.__journal.record("unset", keypath, step=step, index=index)
|
|
346
366
|
except Exception as e:
|
|
347
367
|
new_msg = f"error while unsetting [{','.join(keypath)}]: {e.args[0]}"
|
|
348
368
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -376,6 +396,7 @@ class BaseSchema:
|
|
|
376
396
|
return
|
|
377
397
|
|
|
378
398
|
del key_param.__manifest[removal_key]
|
|
399
|
+
self.__journal.record("remove", keypath)
|
|
379
400
|
|
|
380
401
|
def valid(self, *keypath, default_valid=False, check_complete=False):
|
|
381
402
|
"""
|
|
@@ -470,7 +491,7 @@ class BaseSchema:
|
|
|
470
491
|
add(keys, key, item)
|
|
471
492
|
return set(keys)
|
|
472
493
|
|
|
473
|
-
def getdict(self, *keypath, include_default=True):
|
|
494
|
+
def getdict(self, *keypath, include_default=True, values_only=False):
|
|
474
495
|
"""
|
|
475
496
|
Returns a schema dictionary.
|
|
476
497
|
|
|
@@ -480,6 +501,7 @@ class BaseSchema:
|
|
|
480
501
|
Args:
|
|
481
502
|
keypath (list of str): Variable length ordered schema key list
|
|
482
503
|
include_default (boolean): If true will include default key paths
|
|
504
|
+
values_only (boolean): If true will only return values
|
|
483
505
|
|
|
484
506
|
Returns:
|
|
485
507
|
A schema dictionary
|
|
@@ -493,13 +515,25 @@ class BaseSchema:
|
|
|
493
515
|
key_param = self.__manifest.get(keypath[0], None)
|
|
494
516
|
if not key_param:
|
|
495
517
|
return {}
|
|
496
|
-
return key_param.getdict(*keypath[1:],
|
|
518
|
+
return key_param.getdict(*keypath[1:],
|
|
519
|
+
include_default=include_default,
|
|
520
|
+
values_only=values_only)
|
|
497
521
|
|
|
498
522
|
manifest = {}
|
|
499
523
|
if include_default and self.__default:
|
|
500
|
-
|
|
524
|
+
manifest_dict = self.__default.getdict(include_default=include_default,
|
|
525
|
+
values_only=values_only)
|
|
526
|
+
if manifest_dict or not values_only:
|
|
527
|
+
manifest["default"] = manifest_dict
|
|
501
528
|
for key, item in self.__manifest.items():
|
|
502
|
-
|
|
529
|
+
manifest_dict = item.getdict(include_default=include_default,
|
|
530
|
+
values_only=values_only)
|
|
531
|
+
if manifest_dict or not values_only:
|
|
532
|
+
manifest[key] = manifest_dict
|
|
533
|
+
|
|
534
|
+
if not values_only and self.__journal.has_journaling():
|
|
535
|
+
manifest["__journal__"] = self.__journal.get()
|
|
536
|
+
|
|
503
537
|
return manifest
|
|
504
538
|
|
|
505
539
|
# Utility functions
|
|
@@ -511,4 +545,193 @@ class BaseSchema:
|
|
|
511
545
|
key (list of str): keypath to this schema
|
|
512
546
|
"""
|
|
513
547
|
|
|
514
|
-
|
|
548
|
+
parent = self.__parent
|
|
549
|
+
self.__parent = None
|
|
550
|
+
schema_copy = copy.deepcopy(self)
|
|
551
|
+
self.__parent = parent
|
|
552
|
+
|
|
553
|
+
if self is not self.__parent:
|
|
554
|
+
schema_copy.__parent = self.__parent
|
|
555
|
+
else:
|
|
556
|
+
schema_copy.__parent = schema_copy
|
|
557
|
+
|
|
558
|
+
return schema_copy
|
|
559
|
+
|
|
560
|
+
def find_files(self, *keypath, missing_ok=False, step=None, index=None,
|
|
561
|
+
packages=None, collection_dir=None, cwd=None):
|
|
562
|
+
"""
|
|
563
|
+
Returns absolute paths to files or directories based on the keypath
|
|
564
|
+
provided.
|
|
565
|
+
|
|
566
|
+
The keypath provided must point to a schema parameter of type file, dir,
|
|
567
|
+
or lists of either. Otherwise, it will trigger an error.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
keypath (list of str): Variable length schema key list.
|
|
571
|
+
missing_ok (bool): If True, silently return None when files aren't
|
|
572
|
+
found. If False, print an error and set the error flag.
|
|
573
|
+
step (str): Step name to access for parameters that may be specified
|
|
574
|
+
on a per-node basis.
|
|
575
|
+
index (str): Index name to access for parameters that may be specified
|
|
576
|
+
on a per-node basis.
|
|
577
|
+
packages (dict of resolvers): dirctionary of path resolvers for package
|
|
578
|
+
paths, these can either be a path or a callable function
|
|
579
|
+
collection_dir (path): optional path to a collections directory
|
|
580
|
+
cwd (path): optional path to current working directory, this will default
|
|
581
|
+
to os.getcwd() if not provided.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
If keys points to a scalar entry, returns an absolute path to that
|
|
585
|
+
file/directory, or None if not found. It keys points to a list
|
|
586
|
+
entry, returns a list of either the absolute paths or None for each
|
|
587
|
+
entry, depending on whether it is found.
|
|
588
|
+
|
|
589
|
+
Examples:
|
|
590
|
+
>>> chip.find_files('input', 'verilog')
|
|
591
|
+
Returns a list of absolute paths to source files, as specified in
|
|
592
|
+
the schema.
|
|
593
|
+
"""
|
|
594
|
+
|
|
595
|
+
param = self.get(*keypath, field=None)
|
|
596
|
+
paramtype = param.get(field='type')
|
|
597
|
+
if 'file' not in paramtype and 'dir' not in paramtype:
|
|
598
|
+
raise TypeError(f'Cannot find files on [{",".join(keypath)}], must be a path type')
|
|
599
|
+
|
|
600
|
+
paths = param.get(field=None, step=step, index=index)
|
|
601
|
+
|
|
602
|
+
is_list = True
|
|
603
|
+
if not isinstance(paths, list):
|
|
604
|
+
is_list = False
|
|
605
|
+
if paths.get():
|
|
606
|
+
paths = [paths]
|
|
607
|
+
else:
|
|
608
|
+
paths = []
|
|
609
|
+
|
|
610
|
+
# Ignore collection directory if it does not exist
|
|
611
|
+
if collection_dir and not os.path.exists(collection_dir):
|
|
612
|
+
collection_dir = None
|
|
613
|
+
|
|
614
|
+
if cwd is None:
|
|
615
|
+
cwd = os.getcwd()
|
|
616
|
+
|
|
617
|
+
if packages is None:
|
|
618
|
+
packages = {}
|
|
619
|
+
|
|
620
|
+
resolved_paths = []
|
|
621
|
+
for path in paths:
|
|
622
|
+
search_paths = []
|
|
623
|
+
|
|
624
|
+
package = path.get(field="package")
|
|
625
|
+
if package:
|
|
626
|
+
if package not in packages:
|
|
627
|
+
raise ValueError(f"Resolver for {package} not provided")
|
|
628
|
+
package_path = packages[package]
|
|
629
|
+
if isinstance(package_path, str):
|
|
630
|
+
search_paths.append(os.path.abspath(package_path))
|
|
631
|
+
elif callable(package_path):
|
|
632
|
+
search_paths.append(package_path())
|
|
633
|
+
else:
|
|
634
|
+
raise TypeError(f"Resolver for {package} is not a recognized type")
|
|
635
|
+
else:
|
|
636
|
+
if cwd:
|
|
637
|
+
search_paths.append(os.path.abspath(cwd))
|
|
638
|
+
|
|
639
|
+
try:
|
|
640
|
+
resolved_path = path.resolve_path(search=search_paths,
|
|
641
|
+
collection_dir=collection_dir)
|
|
642
|
+
except FileNotFoundError:
|
|
643
|
+
resolved_path = None
|
|
644
|
+
if not missing_ok:
|
|
645
|
+
if package:
|
|
646
|
+
raise FileNotFoundError(
|
|
647
|
+
f'Could not find "{path.get()}" in {package} [{",".join(keypath)}]')
|
|
648
|
+
else:
|
|
649
|
+
raise FileNotFoundError(
|
|
650
|
+
f'Could not find "{path.get()}" [{",".join(keypath)}]')
|
|
651
|
+
resolved_paths.append(resolved_path)
|
|
652
|
+
|
|
653
|
+
if not is_list:
|
|
654
|
+
if not resolved_paths:
|
|
655
|
+
return None
|
|
656
|
+
return resolved_paths[0]
|
|
657
|
+
return resolved_paths
|
|
658
|
+
|
|
659
|
+
def check_filepaths(self, ignore_keys=None, logger=None,
|
|
660
|
+
packages=None, collection_dir=None, cwd=None):
|
|
661
|
+
'''
|
|
662
|
+
Verifies that paths to all files in manifest are valid.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
ignore_keys (list of keypaths): list of keypaths to ignore while checking
|
|
666
|
+
logger (:class:`logging.Logger`): optional logger to use to report errors
|
|
667
|
+
packages (dict of resolvers): dirctionary of path resolvers for package
|
|
668
|
+
paths, these can either be a path or a callable function
|
|
669
|
+
collection_dir (path): optional path to a collections directory
|
|
670
|
+
cwd (path): optional path to current working directory, this will default
|
|
671
|
+
to os.getcwd() if not provided.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
True if all file paths are valid, otherwise False.
|
|
675
|
+
'''
|
|
676
|
+
|
|
677
|
+
if ignore_keys is None:
|
|
678
|
+
ignore_keys = set()
|
|
679
|
+
else:
|
|
680
|
+
ignore_keys = set([
|
|
681
|
+
tuple(keypath) for keypath in ignore_keys
|
|
682
|
+
])
|
|
683
|
+
|
|
684
|
+
error = False
|
|
685
|
+
|
|
686
|
+
for keypath in self.allkeys():
|
|
687
|
+
if keypath in ignore_keys:
|
|
688
|
+
continue
|
|
689
|
+
|
|
690
|
+
param = self.get(*keypath, field=None)
|
|
691
|
+
paramtype = param.get(field='type')
|
|
692
|
+
|
|
693
|
+
if 'file' not in paramtype and 'dir' not in paramtype:
|
|
694
|
+
continue
|
|
695
|
+
|
|
696
|
+
for check_files, step, index in param.getvalues():
|
|
697
|
+
if not check_files:
|
|
698
|
+
# nothing set so continue
|
|
699
|
+
continue
|
|
700
|
+
|
|
701
|
+
found_files = BaseSchema.find_files(
|
|
702
|
+
self, *keypath, missing_ok=True, step=step, index=index,
|
|
703
|
+
packages=packages, collection_dir=collection_dir, cwd=cwd)
|
|
704
|
+
|
|
705
|
+
if not param.is_list():
|
|
706
|
+
check_files = [check_files]
|
|
707
|
+
found_files = [found_files]
|
|
708
|
+
|
|
709
|
+
for check_file, found_file in zip(check_files, found_files):
|
|
710
|
+
if not found_file:
|
|
711
|
+
error = True
|
|
712
|
+
if logger:
|
|
713
|
+
node_indicator = ""
|
|
714
|
+
if step is not None:
|
|
715
|
+
if index is None:
|
|
716
|
+
node_indicator = f" ({step})"
|
|
717
|
+
else:
|
|
718
|
+
node_indicator = f" ({step}/{index})"
|
|
719
|
+
|
|
720
|
+
logger.error(f"Parameter [{','.join(keypath)}]{node_indicator} path "
|
|
721
|
+
f"{check_file} is invalid")
|
|
722
|
+
|
|
723
|
+
return not error
|
|
724
|
+
|
|
725
|
+
def _parent(self, root=False):
|
|
726
|
+
'''
|
|
727
|
+
Returns the parent of this schema section, if root is true the root parent
|
|
728
|
+
will be returned.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
root (bool): if true, returns the root of the schemas.
|
|
732
|
+
'''
|
|
733
|
+
if not root:
|
|
734
|
+
return self.__parent
|
|
735
|
+
if self.__parent is self:
|
|
736
|
+
return self
|
|
737
|
+
return self.__parent._parent(root=root)
|
|
@@ -29,6 +29,9 @@ class EditableSchema:
|
|
|
29
29
|
if key in self.__schema._BaseSchema__manifest and not clobber:
|
|
30
30
|
raise KeyError(f"[{','.join(fullkey)}] is already defined")
|
|
31
31
|
|
|
32
|
+
if isinstance(value, BaseSchema):
|
|
33
|
+
value._BaseSchema__parent = self.__schema
|
|
34
|
+
|
|
32
35
|
if key == "default":
|
|
33
36
|
self.__schema._BaseSchema__default = value
|
|
34
37
|
else:
|
|
@@ -36,6 +39,7 @@ class EditableSchema:
|
|
|
36
39
|
return
|
|
37
40
|
|
|
38
41
|
new_schema = BaseSchema()
|
|
42
|
+
new_schema._BaseSchema__parent = self.__schema
|
|
39
43
|
if key == "default":
|
|
40
44
|
if self.__schema._BaseSchema__default:
|
|
41
45
|
new_schema = self.__schema._BaseSchema__default
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Journal:
|
|
6
|
+
"""
|
|
7
|
+
This class provides the ability to record the schema transactions:
|
|
8
|
+
:meth:`BaseSchema.set`, :meth:`BaseSchema.add`, :meth:`BaseSchema.remove`,
|
|
9
|
+
:meth:`BaseSchema.unset`, and :meth:`BaseSchema.get`.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
keyprefix (list of str): keypath to prefix on to recorded path
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, keyprefix=None):
|
|
16
|
+
if not keyprefix:
|
|
17
|
+
self.__keyprefix = tuple()
|
|
18
|
+
else:
|
|
19
|
+
self.__keyprefix = tuple(keyprefix)
|
|
20
|
+
|
|
21
|
+
self.__parent = self
|
|
22
|
+
|
|
23
|
+
self.__record_types = set()
|
|
24
|
+
self.stop()
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def keypath(self):
|
|
28
|
+
'''
|
|
29
|
+
Returns the reference key path for this journal
|
|
30
|
+
'''
|
|
31
|
+
|
|
32
|
+
return self.__keyprefix
|
|
33
|
+
|
|
34
|
+
def get_child(self, *keypath):
|
|
35
|
+
'''
|
|
36
|
+
Get a child journal based on a new keypath
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
keypath (list of str): keypath to prefix on to recorded path
|
|
40
|
+
'''
|
|
41
|
+
|
|
42
|
+
child = Journal(keyprefix=[*self.__keyprefix, *keypath])
|
|
43
|
+
child.__parent = self.__parent
|
|
44
|
+
return child
|
|
45
|
+
|
|
46
|
+
def from_dict(self, manifest):
|
|
47
|
+
'''
|
|
48
|
+
Import a journal from a manifest dictionary
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
manifest (dict): Manifest to decode.
|
|
52
|
+
'''
|
|
53
|
+
|
|
54
|
+
self.__journal = manifest
|
|
55
|
+
|
|
56
|
+
def get(self):
|
|
57
|
+
"""
|
|
58
|
+
Returns a copy of the current journal
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
return copy.deepcopy(self.__parent.__journal)
|
|
62
|
+
|
|
63
|
+
def has_journaling(self):
|
|
64
|
+
"""
|
|
65
|
+
Returns true if the schema is currently setup and is the root of the journal and has data
|
|
66
|
+
"""
|
|
67
|
+
return self is self.__parent and bool(self.__journal)
|
|
68
|
+
|
|
69
|
+
def is_journaling(self):
|
|
70
|
+
"""
|
|
71
|
+
Returns true if the schema is currently setup for journaling
|
|
72
|
+
"""
|
|
73
|
+
return self.__parent.__journal is not None
|
|
74
|
+
|
|
75
|
+
def get_types(self):
|
|
76
|
+
"""
|
|
77
|
+
Returns the current schema accesses that are being recorded
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
return self.__parent.__record_types.copy()
|
|
81
|
+
|
|
82
|
+
def add_type(self, value):
|
|
83
|
+
"""
|
|
84
|
+
Adds a new access type to the journal record.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
value (str): access type
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
if value not in ("set", "add", "remove", "unset", "get"):
|
|
91
|
+
raise ValueError(f"{value} is not a valid type")
|
|
92
|
+
|
|
93
|
+
return self.__parent.__record_types.add(value)
|
|
94
|
+
|
|
95
|
+
def remove_type(self, value):
|
|
96
|
+
"""
|
|
97
|
+
Removes a new access type to the journal record.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
value (str): access type
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
self.__parent.__record_types.remove(value)
|
|
105
|
+
except KeyError:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def record(self, record_type, key, value=None, field=None, step=None, index=None):
|
|
109
|
+
'''
|
|
110
|
+
Record the schema transaction
|
|
111
|
+
'''
|
|
112
|
+
|
|
113
|
+
if self.__parent.__journal is None:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if record_type not in self.__parent.__record_types:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
self.__parent.__journal.append({
|
|
120
|
+
"type": record_type,
|
|
121
|
+
"key": tuple([*self.__keyprefix, *key]),
|
|
122
|
+
"value": value,
|
|
123
|
+
"field": field,
|
|
124
|
+
"step": step,
|
|
125
|
+
"index": index
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
def start(self):
|
|
129
|
+
'''
|
|
130
|
+
Start journaling the schema transactions
|
|
131
|
+
'''
|
|
132
|
+
self.__parent.__journal = []
|
|
133
|
+
self.add_type("set")
|
|
134
|
+
self.add_type("add")
|
|
135
|
+
self.add_type("remove")
|
|
136
|
+
self.add_type("unset")
|
|
137
|
+
|
|
138
|
+
def stop(self):
|
|
139
|
+
'''
|
|
140
|
+
Stop journaling the schema transactions
|
|
141
|
+
'''
|
|
142
|
+
self.__parent.__journal = None
|
|
143
|
+
self.__parent.__record_types.clear()
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def replay_file(schema, filepath):
|
|
147
|
+
'''
|
|
148
|
+
Replay a journal into a schema from a manifest
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
schema (:class:`BaseSchema`): schema to replay transactions to
|
|
152
|
+
filepath (path): path to manifest
|
|
153
|
+
'''
|
|
154
|
+
with open(filepath, "r") as fid:
|
|
155
|
+
data = json.load(fid)
|
|
156
|
+
if "__journal__" not in data:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
journal = Journal()
|
|
160
|
+
journal.from_dict(data["__journal__"])
|
|
161
|
+
journal.replay(schema)
|
|
162
|
+
|
|
163
|
+
def replay(self, schema):
|
|
164
|
+
'''
|
|
165
|
+
Replay journal into a schema
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
schema (:class:`BaseSchema`): schema to replay transactions to
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
from .baseschema import BaseSchema
|
|
172
|
+
if not isinstance(schema, BaseSchema):
|
|
173
|
+
raise TypeError(f"schema must be a BaseSchema, not {type(schema)}")
|
|
174
|
+
|
|
175
|
+
if not self.__parent.__journal:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
for action in self.__parent.__journal:
|
|
179
|
+
record_type = action['type']
|
|
180
|
+
keypath = action['key']
|
|
181
|
+
value = action['value']
|
|
182
|
+
field = action['field']
|
|
183
|
+
step = action['step']
|
|
184
|
+
index = action['index']
|
|
185
|
+
if record_type == 'set':
|
|
186
|
+
schema.set(*keypath, value, field=field, step=step, index=index)
|
|
187
|
+
elif record_type == 'add':
|
|
188
|
+
schema.add(*keypath, value, field=field, step=step, index=index)
|
|
189
|
+
elif record_type == 'unset':
|
|
190
|
+
schema.unset(*keypath, step=step, index=index)
|
|
191
|
+
elif record_type == 'remove':
|
|
192
|
+
schema.remove(*keypath)
|
|
193
|
+
elif record_type == 'get':
|
|
194
|
+
continue
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError(f'Unknown record type {record_type}')
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def access(schema):
|
|
200
|
+
'''
|
|
201
|
+
Access a journal from a schema
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
schema (:class:`BaseSchema`): schema to replay transactions to access journal
|
|
205
|
+
'''
|
|
206
|
+
from .baseschema import BaseSchema
|
|
207
|
+
if isinstance(schema, BaseSchema):
|
|
208
|
+
return schema._BaseSchema__journal
|
|
209
|
+
|
|
210
|
+
raise TypeError(f"schema must be a BaseSchema, not {type(schema)}")
|
|
@@ -24,7 +24,60 @@ class NamedSchema(BaseSchema):
|
|
|
24
24
|
'''
|
|
25
25
|
Returns the name of the schema
|
|
26
26
|
'''
|
|
27
|
-
|
|
27
|
+
try:
|
|
28
|
+
return self.__name
|
|
29
|
+
except AttributeError:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
def set_name(self, name):
|
|
33
|
+
"""
|
|
34
|
+
Set the name of this object
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
RuntimeError: if called after object name is set.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
name (str): name for object
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if self.name() is not None:
|
|
44
|
+
raise RuntimeError("Cannot call set_name more than once.")
|
|
45
|
+
self.__name = name
|
|
46
|
+
|
|
47
|
+
def _reset(self) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Resets the state of the object
|
|
50
|
+
"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def type(self) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Returns the type of this object
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError("Must be implemented by the child classes.")
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_manifest(cls, name, filepath=None, cfg=None):
|
|
61
|
+
'''
|
|
62
|
+
Create a new schema based on the provided source files.
|
|
63
|
+
|
|
64
|
+
The two arguments to this method are mutually exclusive.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
name (str): name of the schema
|
|
68
|
+
filepath (path): Initial manifest.
|
|
69
|
+
cfg (dict): Initial configuration dictionary.
|
|
70
|
+
'''
|
|
71
|
+
|
|
72
|
+
if not filepath and cfg is None:
|
|
73
|
+
raise RuntimeError("filepath or dictionary is required")
|
|
74
|
+
|
|
75
|
+
schema = cls(name)
|
|
76
|
+
if filepath:
|
|
77
|
+
schema.read_manifest(filepath)
|
|
78
|
+
if cfg:
|
|
79
|
+
schema._from_dict(cfg, [])
|
|
80
|
+
return schema
|
|
28
81
|
|
|
29
82
|
def _from_dict(self, manifest, keypath, version=None):
|
|
30
83
|
if keypath:
|
|
@@ -35,7 +88,7 @@ class NamedSchema(BaseSchema):
|
|
|
35
88
|
def copy(self, key=None):
|
|
36
89
|
copy = super().copy(key=key)
|
|
37
90
|
|
|
38
|
-
if
|
|
91
|
+
if key and key[-1] != "default":
|
|
39
92
|
copy.__name = key[-1]
|
|
40
93
|
|
|
41
94
|
return copy
|
|
@@ -409,12 +409,13 @@ class Parameter:
|
|
|
409
409
|
|
|
410
410
|
return True
|
|
411
411
|
|
|
412
|
-
def getdict(self, include_default=True):
|
|
412
|
+
def getdict(self, include_default=True, values_only=False):
|
|
413
413
|
"""
|
|
414
414
|
Returns a schema dictionary.
|
|
415
415
|
|
|
416
416
|
Args:
|
|
417
417
|
include_default (boolean): If true will include default values
|
|
418
|
+
values_only (boolean): If true will only return values
|
|
418
419
|
|
|
419
420
|
Returns:
|
|
420
421
|
A schema dictionary
|
|
@@ -424,6 +425,18 @@ class Parameter:
|
|
|
424
425
|
Returns the complete dictionary for the parameter
|
|
425
426
|
"""
|
|
426
427
|
|
|
428
|
+
if values_only:
|
|
429
|
+
dictvals = {}
|
|
430
|
+
is_list = self.is_list()
|
|
431
|
+
for value, step, index in self.getvalues(return_defvalue=include_default):
|
|
432
|
+
if is_list:
|
|
433
|
+
if value:
|
|
434
|
+
dictvals.setdefault(step, {})[index] = value
|
|
435
|
+
else:
|
|
436
|
+
if value is not None:
|
|
437
|
+
dictvals.setdefault(step, {})[index] = value
|
|
438
|
+
return dictvals
|
|
439
|
+
|
|
427
440
|
dictvals = {
|
|
428
441
|
"type": NodeType.encode(self.__type),
|
|
429
442
|
"require": self.__require,
|