siliconcompiler 0.33.1__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/apps/utils/replay.py +5 -5
- siliconcompiler/checklist.py +1 -1
- siliconcompiler/core.py +39 -48
- siliconcompiler/data/templates/replay/replay.sh.j2 +18 -1
- siliconcompiler/dependencyschema.py +392 -0
- siliconcompiler/design.py +664 -0
- siliconcompiler/flowgraph.py +32 -1
- siliconcompiler/metric.py +19 -0
- 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/record.py +57 -5
- siliconcompiler/remote/client.py +61 -13
- siliconcompiler/remote/server.py +109 -64
- siliconcompiler/report/dashboard/cli/board.py +1 -2
- siliconcompiler/scheduler/__init__.py +3 -1375
- siliconcompiler/scheduler/docker.py +268 -0
- siliconcompiler/scheduler/run_node.py +20 -19
- 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 +256 -11
- 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 +211 -350
- siliconcompiler/tool.py +139 -37
- siliconcompiler/tools/_common/__init__.py +14 -11
- 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/slang/__init__.py +3 -2
- siliconcompiler/tools/vpr/route.py +69 -0
- siliconcompiler/tools/yosys/sc_synth_asic.tcl +0 -4
- siliconcompiler/toolscripts/_tools.json +13 -8
- siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +4 -0
- siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +4 -0
- siliconcompiler/utils/__init__.py +2 -23
- siliconcompiler/utils/flowgraph.py +5 -5
- siliconcompiler/utils/logging.py +2 -1
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/METADATA +8 -6
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/RECORD +57 -52
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/WHEEL +1 -1
- siliconcompiler/scheduler/docker_runner.py +0 -254
- siliconcompiler/schema/journalingschema.py +0 -238
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.33.1.dist-info → siliconcompiler-0.34.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.33.1.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,12 +227,22 @@ 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")
|
|
226
235
|
if field is None:
|
|
227
236
|
return param
|
|
228
|
-
|
|
237
|
+
|
|
238
|
+
try:
|
|
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
|
|
242
|
+
except Exception as e:
|
|
243
|
+
new_msg = f"error while accessing [{','.join(keypath)}]: {e.args[0]}"
|
|
244
|
+
e.args = (new_msg, *e.args[1:])
|
|
245
|
+
raise e
|
|
229
246
|
|
|
230
247
|
def set(self, *args, field='value', clobber=True, step=None, index=None):
|
|
231
248
|
'''
|
|
@@ -259,7 +276,17 @@ class BaseSchema:
|
|
|
259
276
|
except KeyError:
|
|
260
277
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
261
278
|
|
|
262
|
-
|
|
279
|
+
try:
|
|
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
|
|
286
|
+
except Exception as e:
|
|
287
|
+
new_msg = f"error while setting [{','.join(keypath)}]: {e.args[0]}"
|
|
288
|
+
e.args = (new_msg, *e.args[1:])
|
|
289
|
+
raise e
|
|
263
290
|
|
|
264
291
|
def add(self, *args, field='value', step=None, index=None):
|
|
265
292
|
'''
|
|
@@ -292,7 +319,16 @@ class BaseSchema:
|
|
|
292
319
|
except KeyError:
|
|
293
320
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
294
321
|
|
|
295
|
-
|
|
322
|
+
try:
|
|
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
|
|
328
|
+
except Exception as e:
|
|
329
|
+
new_msg = f"error while adding to [{','.join(keypath)}]: {e.args[0]}"
|
|
330
|
+
e.args = (new_msg, *e.args[1:])
|
|
331
|
+
raise e
|
|
296
332
|
|
|
297
333
|
def unset(self, *keypath, step=None, index=None):
|
|
298
334
|
'''
|
|
@@ -325,7 +361,13 @@ class BaseSchema:
|
|
|
325
361
|
except KeyError:
|
|
326
362
|
raise KeyError(f"[{','.join(keypath)}] is not a valid keypath")
|
|
327
363
|
|
|
328
|
-
|
|
364
|
+
try:
|
|
365
|
+
param.unset(step=step, index=index)
|
|
366
|
+
self.__journal.record("unset", keypath, step=step, index=index)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
new_msg = f"error while unsetting [{','.join(keypath)}]: {e.args[0]}"
|
|
369
|
+
e.args = (new_msg, *e.args[1:])
|
|
370
|
+
raise e
|
|
329
371
|
|
|
330
372
|
def remove(self, *keypath):
|
|
331
373
|
'''
|
|
@@ -355,6 +397,7 @@ class BaseSchema:
|
|
|
355
397
|
return
|
|
356
398
|
|
|
357
399
|
del key_param.__manifest[removal_key]
|
|
400
|
+
self.__journal.record("remove", keypath)
|
|
358
401
|
|
|
359
402
|
def valid(self, *keypath, default_valid=False, check_complete=False):
|
|
360
403
|
"""
|
|
@@ -449,7 +492,7 @@ class BaseSchema:
|
|
|
449
492
|
add(keys, key, item)
|
|
450
493
|
return set(keys)
|
|
451
494
|
|
|
452
|
-
def getdict(self, *keypath, include_default=True):
|
|
495
|
+
def getdict(self, *keypath, include_default=True, values_only=False):
|
|
453
496
|
"""
|
|
454
497
|
Returns a schema dictionary.
|
|
455
498
|
|
|
@@ -459,6 +502,7 @@ class BaseSchema:
|
|
|
459
502
|
Args:
|
|
460
503
|
keypath (list of str): Variable length ordered schema key list
|
|
461
504
|
include_default (boolean): If true will include default key paths
|
|
505
|
+
values_only (boolean): If true will only return values
|
|
462
506
|
|
|
463
507
|
Returns:
|
|
464
508
|
A schema dictionary
|
|
@@ -472,13 +516,25 @@ class BaseSchema:
|
|
|
472
516
|
key_param = self.__manifest.get(keypath[0], None)
|
|
473
517
|
if not key_param:
|
|
474
518
|
return {}
|
|
475
|
-
return key_param.getdict(*keypath[1:],
|
|
519
|
+
return key_param.getdict(*keypath[1:],
|
|
520
|
+
include_default=include_default,
|
|
521
|
+
values_only=values_only)
|
|
476
522
|
|
|
477
523
|
manifest = {}
|
|
478
524
|
if include_default and self.__default:
|
|
479
|
-
|
|
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
|
|
480
529
|
for key, item in self.__manifest.items():
|
|
481
|
-
|
|
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
|
+
|
|
482
538
|
return manifest
|
|
483
539
|
|
|
484
540
|
# Utility functions
|
|
@@ -490,4 +546,193 @@ class BaseSchema:
|
|
|
490
546
|
key (list of str): keypath to this schema
|
|
491
547
|
"""
|
|
492
548
|
|
|
493
|
-
|
|
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,
|