siliconcompiler 0.33.2__py3-none-any.whl → 0.34.0__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/sc_issue.py +5 -3
- siliconcompiler/apps/sc_remote.py +0 -17
- siliconcompiler/checklist.py +1 -1
- siliconcompiler/core.py +34 -47
- siliconcompiler/dependencyschema.py +392 -0
- siliconcompiler/design.py +664 -0
- siliconcompiler/flowgraph.py +32 -1
- 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 +2 -2
- siliconcompiler/remote/client.py +15 -3
- siliconcompiler/report/dashboard/cli/board.py +1 -1
- siliconcompiler/scheduler/__init__.py +3 -1382
- siliconcompiler/scheduler/docker.py +268 -0
- siliconcompiler/scheduler/run_node.py +10 -16
- siliconcompiler/scheduler/scheduler.py +308 -0
- siliconcompiler/scheduler/schedulernode.py +934 -0
- siliconcompiler/scheduler/slurm.py +147 -163
- siliconcompiler/scheduler/taskscheduler.py +39 -52
- siliconcompiler/schema/__init__.py +3 -3
- siliconcompiler/schema/baseschema.py +234 -10
- siliconcompiler/schema/editableschema.py +4 -0
- siliconcompiler/schema/journal.py +210 -0
- siliconcompiler/schema/namedschema.py +31 -2
- siliconcompiler/schema/parameter.py +14 -1
- siliconcompiler/schema/parametervalue.py +1 -34
- siliconcompiler/schema/schema_cfg.py +210 -349
- siliconcompiler/tool.py +61 -20
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/verify.py +1 -2
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +27 -25
- siliconcompiler/tools/vpr/route.py +69 -0
- siliconcompiler/toolscripts/_tools.json +4 -4
- siliconcompiler/utils/__init__.py +2 -23
- siliconcompiler/utils/flowgraph.py +5 -5
- siliconcompiler/utils/logging.py +2 -1
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/METADATA +4 -3
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/RECORD +47 -42
- siliconcompiler/scheduler/docker_runner.py +0 -254
- siliconcompiler/schema/journalingschema.py +0 -242
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.0.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:
|
|
@@ -34,6 +35,8 @@ class BaseSchema:
|
|
|
34
35
|
# Data storage for the schema
|
|
35
36
|
self.__manifest = {}
|
|
36
37
|
self.__default = None
|
|
38
|
+
self.__journal = Journal()
|
|
39
|
+
self.__parent = self
|
|
37
40
|
|
|
38
41
|
def _from_dict(self, manifest, keypath, version=None):
|
|
39
42
|
'''
|
|
@@ -48,6 +51,9 @@ class BaseSchema:
|
|
|
48
51
|
handled = set()
|
|
49
52
|
missing = set()
|
|
50
53
|
|
|
54
|
+
if "__journal__" in manifest:
|
|
55
|
+
self.__journal.from_dict(manifest["__journal__"])
|
|
56
|
+
|
|
51
57
|
if self.__default:
|
|
52
58
|
data = manifest.get("default", None)
|
|
53
59
|
if data:
|
|
@@ -74,16 +80,17 @@ class BaseSchema:
|
|
|
74
80
|
'''
|
|
75
81
|
Create a new schema based on the provided source files.
|
|
76
82
|
|
|
77
|
-
The two arguments to this
|
|
83
|
+
The two arguments to this method are mutually exclusive.
|
|
78
84
|
|
|
79
85
|
Args:
|
|
80
86
|
filepath (path): Initial manifest.
|
|
81
87
|
cfg (dict): Initial configuration dictionary.
|
|
82
88
|
'''
|
|
83
89
|
|
|
84
|
-
schema = cls()
|
|
85
90
|
if not filepath and cfg is None:
|
|
86
91
|
raise RuntimeError("filepath or dictionary is required")
|
|
92
|
+
|
|
93
|
+
schema = cls()
|
|
87
94
|
if filepath:
|
|
88
95
|
schema.read_manifest(filepath)
|
|
89
96
|
if cfg:
|
|
@@ -220,6 +227,8 @@ class BaseSchema:
|
|
|
220
227
|
if field == 'schema':
|
|
221
228
|
if isinstance(param, Parameter):
|
|
222
229
|
raise ValueError(f"[{','.join(keypath)}] is a complete keypath")
|
|
230
|
+
self.__journal.record("get", keypath, field=field, step=step, index=index)
|
|
231
|
+
param.__journal = self.__journal.get_child(*keypath)
|
|
223
232
|
return param
|
|
224
233
|
except KeyError:
|
|
225
234
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
@@ -227,7 +236,9 @@ class BaseSchema:
|
|
|
227
236
|
return param
|
|
228
237
|
|
|
229
238
|
try:
|
|
230
|
-
|
|
239
|
+
get_ret = param.get(field, step=step, index=index)
|
|
240
|
+
self.__journal.record("get", keypath, field=field, step=step, index=index)
|
|
241
|
+
return get_ret
|
|
231
242
|
except Exception as e:
|
|
232
243
|
new_msg = f"error while accessing [{','.join(keypath)}]: {e.args[0]}"
|
|
233
244
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -266,7 +277,12 @@ class BaseSchema:
|
|
|
266
277
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
267
278
|
|
|
268
279
|
try:
|
|
269
|
-
|
|
280
|
+
set_ret = param.set(value, field=field, clobber=clobber,
|
|
281
|
+
step=step, index=index)
|
|
282
|
+
if set_ret:
|
|
283
|
+
self.__journal.record("set", keypath, value=value, field=field,
|
|
284
|
+
step=step, index=index)
|
|
285
|
+
return set_ret
|
|
270
286
|
except Exception as e:
|
|
271
287
|
new_msg = f"error while setting [{','.join(keypath)}]: {e.args[0]}"
|
|
272
288
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -304,7 +320,11 @@ class BaseSchema:
|
|
|
304
320
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
305
321
|
|
|
306
322
|
try:
|
|
307
|
-
|
|
323
|
+
add_ret = param.add(value, field=field, step=step, index=index)
|
|
324
|
+
if add_ret:
|
|
325
|
+
self.__journal.record("add", keypath, value=value, field=field,
|
|
326
|
+
step=step, index=index)
|
|
327
|
+
return add_ret
|
|
308
328
|
except Exception as e:
|
|
309
329
|
new_msg = f"error while adding to [{','.join(keypath)}]: {e.args[0]}"
|
|
310
330
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -343,6 +363,7 @@ class BaseSchema:
|
|
|
343
363
|
|
|
344
364
|
try:
|
|
345
365
|
param.unset(step=step, index=index)
|
|
366
|
+
self.__journal.record("unset", keypath, step=step, index=index)
|
|
346
367
|
except Exception as e:
|
|
347
368
|
new_msg = f"error while unsetting [{','.join(keypath)}]: {e.args[0]}"
|
|
348
369
|
e.args = (new_msg, *e.args[1:])
|
|
@@ -376,6 +397,7 @@ class BaseSchema:
|
|
|
376
397
|
return
|
|
377
398
|
|
|
378
399
|
del key_param.__manifest[removal_key]
|
|
400
|
+
self.__journal.record("remove", keypath)
|
|
379
401
|
|
|
380
402
|
def valid(self, *keypath, default_valid=False, check_complete=False):
|
|
381
403
|
"""
|
|
@@ -470,7 +492,7 @@ class BaseSchema:
|
|
|
470
492
|
add(keys, key, item)
|
|
471
493
|
return set(keys)
|
|
472
494
|
|
|
473
|
-
def getdict(self, *keypath, include_default=True):
|
|
495
|
+
def getdict(self, *keypath, include_default=True, values_only=False):
|
|
474
496
|
"""
|
|
475
497
|
Returns a schema dictionary.
|
|
476
498
|
|
|
@@ -480,6 +502,7 @@ class BaseSchema:
|
|
|
480
502
|
Args:
|
|
481
503
|
keypath (list of str): Variable length ordered schema key list
|
|
482
504
|
include_default (boolean): If true will include default key paths
|
|
505
|
+
values_only (boolean): If true will only return values
|
|
483
506
|
|
|
484
507
|
Returns:
|
|
485
508
|
A schema dictionary
|
|
@@ -493,13 +516,25 @@ class BaseSchema:
|
|
|
493
516
|
key_param = self.__manifest.get(keypath[0], None)
|
|
494
517
|
if not key_param:
|
|
495
518
|
return {}
|
|
496
|
-
return key_param.getdict(*keypath[1:],
|
|
519
|
+
return key_param.getdict(*keypath[1:],
|
|
520
|
+
include_default=include_default,
|
|
521
|
+
values_only=values_only)
|
|
497
522
|
|
|
498
523
|
manifest = {}
|
|
499
524
|
if include_default and self.__default:
|
|
500
|
-
|
|
525
|
+
manifest_dict = self.__default.getdict(include_default=include_default,
|
|
526
|
+
values_only=values_only)
|
|
527
|
+
if manifest_dict or not values_only:
|
|
528
|
+
manifest["default"] = manifest_dict
|
|
501
529
|
for key, item in self.__manifest.items():
|
|
502
|
-
|
|
530
|
+
manifest_dict = item.getdict(include_default=include_default,
|
|
531
|
+
values_only=values_only)
|
|
532
|
+
if manifest_dict or not values_only:
|
|
533
|
+
manifest[key] = manifest_dict
|
|
534
|
+
|
|
535
|
+
if not values_only and self.__journal.has_journaling():
|
|
536
|
+
manifest["__journal__"] = self.__journal.get()
|
|
537
|
+
|
|
503
538
|
return manifest
|
|
504
539
|
|
|
505
540
|
# Utility functions
|
|
@@ -511,4 +546,193 @@ class BaseSchema:
|
|
|
511
546
|
key (list of str): keypath to this schema
|
|
512
547
|
"""
|
|
513
548
|
|
|
514
|
-
|
|
549
|
+
parent = self.__parent
|
|
550
|
+
self.__parent = None
|
|
551
|
+
schema_copy = copy.deepcopy(self)
|
|
552
|
+
self.__parent = parent
|
|
553
|
+
|
|
554
|
+
if self is not self.__parent:
|
|
555
|
+
schema_copy.__parent = self.__parent
|
|
556
|
+
else:
|
|
557
|
+
schema_copy.__parent = schema_copy
|
|
558
|
+
|
|
559
|
+
return schema_copy
|
|
560
|
+
|
|
561
|
+
def find_files(self, *keypath, missing_ok=False, step=None, index=None,
|
|
562
|
+
packages=None, collection_dir=None, cwd=None):
|
|
563
|
+
"""
|
|
564
|
+
Returns absolute paths to files or directories based on the keypath
|
|
565
|
+
provided.
|
|
566
|
+
|
|
567
|
+
The keypath provided must point to a schema parameter of type file, dir,
|
|
568
|
+
or lists of either. Otherwise, it will trigger an error.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
keypath (list of str): Variable length schema key list.
|
|
572
|
+
missing_ok (bool): If True, silently return None when files aren't
|
|
573
|
+
found. If False, print an error and set the error flag.
|
|
574
|
+
step (str): Step name to access for parameters that may be specified
|
|
575
|
+
on a per-node basis.
|
|
576
|
+
index (str): Index name to access for parameters that may be specified
|
|
577
|
+
on a per-node basis.
|
|
578
|
+
packages (dict of resolvers): dirctionary of path resolvers for package
|
|
579
|
+
paths, these can either be a path or a callable function
|
|
580
|
+
collection_dir (path): optional path to a collections directory
|
|
581
|
+
cwd (path): optional path to current working directory, this will default
|
|
582
|
+
to os.getcwd() if not provided.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
If keys points to a scalar entry, returns an absolute path to that
|
|
586
|
+
file/directory, or None if not found. It keys points to a list
|
|
587
|
+
entry, returns a list of either the absolute paths or None for each
|
|
588
|
+
entry, depending on whether it is found.
|
|
589
|
+
|
|
590
|
+
Examples:
|
|
591
|
+
>>> chip.find_files('input', 'verilog')
|
|
592
|
+
Returns a list of absolute paths to source files, as specified in
|
|
593
|
+
the schema.
|
|
594
|
+
"""
|
|
595
|
+
|
|
596
|
+
param = self.get(*keypath, field=None)
|
|
597
|
+
paramtype = param.get(field='type')
|
|
598
|
+
if 'file' not in paramtype and 'dir' not in paramtype:
|
|
599
|
+
raise TypeError(f'Cannot find files on [{",".join(keypath)}], must be a path type')
|
|
600
|
+
|
|
601
|
+
paths = param.get(field=None, step=step, index=index)
|
|
602
|
+
|
|
603
|
+
is_list = True
|
|
604
|
+
if not isinstance(paths, list):
|
|
605
|
+
is_list = False
|
|
606
|
+
if paths.get():
|
|
607
|
+
paths = [paths]
|
|
608
|
+
else:
|
|
609
|
+
paths = []
|
|
610
|
+
|
|
611
|
+
# Ignore collection directory if it does not exist
|
|
612
|
+
if collection_dir and not os.path.exists(collection_dir):
|
|
613
|
+
collection_dir = None
|
|
614
|
+
|
|
615
|
+
if cwd is None:
|
|
616
|
+
cwd = os.getcwd()
|
|
617
|
+
|
|
618
|
+
if packages is None:
|
|
619
|
+
packages = {}
|
|
620
|
+
|
|
621
|
+
resolved_paths = []
|
|
622
|
+
for path in paths:
|
|
623
|
+
search_paths = []
|
|
624
|
+
|
|
625
|
+
package = path.get(field="package")
|
|
626
|
+
if package:
|
|
627
|
+
if package not in packages:
|
|
628
|
+
raise ValueError(f"Resolver for {package} not provided")
|
|
629
|
+
package_path = packages[package]
|
|
630
|
+
if isinstance(package_path, str):
|
|
631
|
+
search_paths.append(os.path.abspath(package_path))
|
|
632
|
+
elif callable(package_path):
|
|
633
|
+
search_paths.append(package_path())
|
|
634
|
+
else:
|
|
635
|
+
raise TypeError(f"Resolver for {package} is not a recognized type")
|
|
636
|
+
else:
|
|
637
|
+
if cwd:
|
|
638
|
+
search_paths.append(os.path.abspath(cwd))
|
|
639
|
+
|
|
640
|
+
try:
|
|
641
|
+
resolved_path = path.resolve_path(search=search_paths,
|
|
642
|
+
collection_dir=collection_dir)
|
|
643
|
+
except FileNotFoundError:
|
|
644
|
+
resolved_path = None
|
|
645
|
+
if not missing_ok:
|
|
646
|
+
if package:
|
|
647
|
+
raise FileNotFoundError(
|
|
648
|
+
f'Could not find "{path.get()}" in {package} [{",".join(keypath)}]')
|
|
649
|
+
else:
|
|
650
|
+
raise FileNotFoundError(
|
|
651
|
+
f'Could not find "{path.get()}" [{",".join(keypath)}]')
|
|
652
|
+
resolved_paths.append(resolved_path)
|
|
653
|
+
|
|
654
|
+
if not is_list:
|
|
655
|
+
if not resolved_paths:
|
|
656
|
+
return None
|
|
657
|
+
return resolved_paths[0]
|
|
658
|
+
return resolved_paths
|
|
659
|
+
|
|
660
|
+
def check_filepaths(self, ignore_keys=None, logger=None,
|
|
661
|
+
packages=None, collection_dir=None, cwd=None):
|
|
662
|
+
'''
|
|
663
|
+
Verifies that paths to all files in manifest are valid.
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
ignore_keys (list of keypaths): list of keypaths to ignore while checking
|
|
667
|
+
logger (:class:`logging.Logger`): optional logger to use to report errors
|
|
668
|
+
packages (dict of resolvers): dirctionary of path resolvers for package
|
|
669
|
+
paths, these can either be a path or a callable function
|
|
670
|
+
collection_dir (path): optional path to a collections directory
|
|
671
|
+
cwd (path): optional path to current working directory, this will default
|
|
672
|
+
to os.getcwd() if not provided.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
True if all file paths are valid, otherwise False.
|
|
676
|
+
'''
|
|
677
|
+
|
|
678
|
+
if ignore_keys is None:
|
|
679
|
+
ignore_keys = set()
|
|
680
|
+
else:
|
|
681
|
+
ignore_keys = set([
|
|
682
|
+
tuple(keypath) for keypath in ignore_keys
|
|
683
|
+
])
|
|
684
|
+
|
|
685
|
+
error = False
|
|
686
|
+
|
|
687
|
+
for keypath in self.allkeys():
|
|
688
|
+
if keypath in ignore_keys:
|
|
689
|
+
continue
|
|
690
|
+
|
|
691
|
+
param = self.get(*keypath, field=None)
|
|
692
|
+
paramtype = param.get(field='type')
|
|
693
|
+
|
|
694
|
+
if 'file' not in paramtype and 'dir' not in paramtype:
|
|
695
|
+
continue
|
|
696
|
+
|
|
697
|
+
for check_files, step, index in param.getvalues():
|
|
698
|
+
if not check_files:
|
|
699
|
+
# nothing set so continue
|
|
700
|
+
continue
|
|
701
|
+
|
|
702
|
+
found_files = BaseSchema.find_files(
|
|
703
|
+
self, *keypath, missing_ok=True, step=step, index=index,
|
|
704
|
+
packages=packages, collection_dir=collection_dir, cwd=cwd)
|
|
705
|
+
|
|
706
|
+
if not param.is_list():
|
|
707
|
+
check_files = [check_files]
|
|
708
|
+
found_files = [found_files]
|
|
709
|
+
|
|
710
|
+
for check_file, found_file in zip(check_files, found_files):
|
|
711
|
+
if not found_file:
|
|
712
|
+
error = True
|
|
713
|
+
if logger:
|
|
714
|
+
node_indicator = ""
|
|
715
|
+
if step is not None:
|
|
716
|
+
if index is None:
|
|
717
|
+
node_indicator = f" ({step})"
|
|
718
|
+
else:
|
|
719
|
+
node_indicator = f" ({step}{index})"
|
|
720
|
+
|
|
721
|
+
logger.error(f"Parameter [{','.join(keypath)}]{node_indicator} path "
|
|
722
|
+
f"{check_file} is invalid")
|
|
723
|
+
|
|
724
|
+
return not error
|
|
725
|
+
|
|
726
|
+
def _parent(self, root=False):
|
|
727
|
+
'''
|
|
728
|
+
Returns the parent of this schema section, if root is true the root parent
|
|
729
|
+
will be returned.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
root (bool): if true, returns the root of the schemas.
|
|
733
|
+
'''
|
|
734
|
+
if not root:
|
|
735
|
+
return self.__parent
|
|
736
|
+
if self.__parent is self:
|
|
737
|
+
return self
|
|
738
|
+
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)}")
|
|
@@ -15,7 +15,7 @@ class NamedSchema(BaseSchema):
|
|
|
15
15
|
name (str): name of the schema
|
|
16
16
|
'''
|
|
17
17
|
|
|
18
|
-
def __init__(self, name
|
|
18
|
+
def __init__(self, name):
|
|
19
19
|
super().__init__()
|
|
20
20
|
|
|
21
21
|
self.__name = name
|
|
@@ -26,6 +26,35 @@ class NamedSchema(BaseSchema):
|
|
|
26
26
|
'''
|
|
27
27
|
return self.__name
|
|
28
28
|
|
|
29
|
+
def _reset(self) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Resets the state of the object
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_manifest(cls, name, filepath=None, cfg=None):
|
|
37
|
+
'''
|
|
38
|
+
Create a new schema based on the provided source files.
|
|
39
|
+
|
|
40
|
+
The two arguments to this method are mutually exclusive.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name (str): name of the schema
|
|
44
|
+
filepath (path): Initial manifest.
|
|
45
|
+
cfg (dict): Initial configuration dictionary.
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
if not filepath and cfg is None:
|
|
49
|
+
raise RuntimeError("filepath or dictionary is required")
|
|
50
|
+
|
|
51
|
+
schema = cls(name)
|
|
52
|
+
if filepath:
|
|
53
|
+
schema.read_manifest(filepath)
|
|
54
|
+
if cfg:
|
|
55
|
+
schema._from_dict(cfg, [])
|
|
56
|
+
return schema
|
|
57
|
+
|
|
29
58
|
def _from_dict(self, manifest, keypath, version=None):
|
|
30
59
|
if keypath:
|
|
31
60
|
self.__name = keypath[-1]
|
|
@@ -35,7 +64,7 @@ class NamedSchema(BaseSchema):
|
|
|
35
64
|
def copy(self, key=None):
|
|
36
65
|
copy = super().copy(key=key)
|
|
37
66
|
|
|
38
|
-
if
|
|
67
|
+
if key and key[-1] != "default":
|
|
39
68
|
copy.__name = key[-1]
|
|
40
69
|
|
|
41
70
|
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,
|