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.
Files changed (104) hide show
  1. siliconcompiler/__init__.py +2 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/_common.py +1 -1
  4. siliconcompiler/apps/sc.py +1 -1
  5. siliconcompiler/apps/sc_issue.py +6 -4
  6. siliconcompiler/apps/sc_remote.py +3 -20
  7. siliconcompiler/apps/sc_show.py +2 -2
  8. siliconcompiler/apps/utils/replay.py +4 -4
  9. siliconcompiler/checklist.py +202 -1
  10. siliconcompiler/core.py +62 -293
  11. siliconcompiler/data/templates/email/general.j2 +3 -3
  12. siliconcompiler/data/templates/email/summary.j2 +1 -1
  13. siliconcompiler/data/templates/issue/README.txt +1 -1
  14. siliconcompiler/data/templates/report/sc_report.j2 +7 -7
  15. siliconcompiler/dependencyschema.py +392 -0
  16. siliconcompiler/design.py +758 -0
  17. siliconcompiler/flowgraph.py +79 -13
  18. siliconcompiler/optimizer/vizier.py +2 -2
  19. siliconcompiler/package/__init__.py +383 -223
  20. siliconcompiler/package/git.py +75 -77
  21. siliconcompiler/package/github.py +70 -97
  22. siliconcompiler/package/https.py +77 -93
  23. siliconcompiler/packageschema.py +260 -0
  24. siliconcompiler/pdk.py +5 -5
  25. siliconcompiler/remote/client.py +33 -15
  26. siliconcompiler/remote/server.py +2 -2
  27. siliconcompiler/report/dashboard/cli/__init__.py +6 -6
  28. siliconcompiler/report/dashboard/cli/board.py +4 -4
  29. siliconcompiler/report/dashboard/web/components/__init__.py +5 -5
  30. siliconcompiler/report/dashboard/web/components/flowgraph.py +4 -4
  31. siliconcompiler/report/dashboard/web/components/graph.py +2 -2
  32. siliconcompiler/report/dashboard/web/state.py +1 -1
  33. siliconcompiler/report/dashboard/web/utils/__init__.py +5 -5
  34. siliconcompiler/report/html_report.py +1 -1
  35. siliconcompiler/report/report.py +4 -4
  36. siliconcompiler/report/summary_table.py +2 -2
  37. siliconcompiler/report/utils.py +5 -5
  38. siliconcompiler/scheduler/__init__.py +3 -1382
  39. siliconcompiler/scheduler/docker.py +263 -0
  40. siliconcompiler/scheduler/run_node.py +10 -21
  41. siliconcompiler/scheduler/scheduler.py +311 -0
  42. siliconcompiler/scheduler/schedulernode.py +944 -0
  43. siliconcompiler/scheduler/send_messages.py +3 -3
  44. siliconcompiler/scheduler/slurm.py +149 -163
  45. siliconcompiler/scheduler/taskscheduler.py +45 -57
  46. siliconcompiler/schema/__init__.py +3 -3
  47. siliconcompiler/schema/baseschema.py +234 -11
  48. siliconcompiler/schema/editableschema.py +4 -0
  49. siliconcompiler/schema/journal.py +210 -0
  50. siliconcompiler/schema/namedschema.py +55 -2
  51. siliconcompiler/schema/parameter.py +14 -1
  52. siliconcompiler/schema/parametervalue.py +1 -34
  53. siliconcompiler/schema/schema_cfg.py +210 -349
  54. siliconcompiler/tool.py +412 -148
  55. siliconcompiler/tools/__init__.py +2 -0
  56. siliconcompiler/tools/builtin/_common.py +5 -5
  57. siliconcompiler/tools/builtin/concatenate.py +7 -7
  58. siliconcompiler/tools/builtin/minimum.py +4 -4
  59. siliconcompiler/tools/builtin/mux.py +4 -4
  60. siliconcompiler/tools/builtin/nop.py +4 -4
  61. siliconcompiler/tools/builtin/verify.py +8 -9
  62. siliconcompiler/tools/execute/exec_input.py +1 -1
  63. siliconcompiler/tools/genfasm/genfasm.py +1 -6
  64. siliconcompiler/tools/openroad/_apr.py +5 -1
  65. siliconcompiler/tools/openroad/antenna_repair.py +1 -1
  66. siliconcompiler/tools/openroad/macro_placement.py +1 -1
  67. siliconcompiler/tools/openroad/power_grid.py +1 -1
  68. siliconcompiler/tools/openroad/scripts/common/procs.tcl +32 -25
  69. siliconcompiler/tools/opensta/timing.py +26 -3
  70. siliconcompiler/tools/slang/__init__.py +2 -2
  71. siliconcompiler/tools/surfer/__init__.py +0 -0
  72. siliconcompiler/tools/surfer/show.py +53 -0
  73. siliconcompiler/tools/surfer/surfer.py +30 -0
  74. siliconcompiler/tools/vpr/route.py +82 -0
  75. siliconcompiler/tools/vpr/vpr.py +23 -6
  76. siliconcompiler/tools/yosys/__init__.py +1 -1
  77. siliconcompiler/tools/yosys/scripts/procs.tcl +143 -0
  78. siliconcompiler/tools/yosys/{sc_synth_asic.tcl → scripts/sc_synth_asic.tcl} +4 -0
  79. siliconcompiler/tools/yosys/{sc_synth_fpga.tcl → scripts/sc_synth_fpga.tcl} +24 -77
  80. siliconcompiler/tools/yosys/syn_fpga.py +14 -0
  81. siliconcompiler/toolscripts/_tools.json +9 -13
  82. siliconcompiler/toolscripts/rhel9/install-vpr.sh +0 -2
  83. siliconcompiler/toolscripts/ubuntu22/install-surfer.sh +33 -0
  84. siliconcompiler/toolscripts/ubuntu24/install-surfer.sh +33 -0
  85. siliconcompiler/utils/__init__.py +4 -24
  86. siliconcompiler/utils/flowgraph.py +29 -28
  87. siliconcompiler/utils/issue.py +23 -29
  88. siliconcompiler/utils/logging.py +37 -7
  89. siliconcompiler/utils/showtools.py +6 -1
  90. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/METADATA +16 -25
  91. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/RECORD +98 -91
  92. siliconcompiler/scheduler/docker_runner.py +0 -254
  93. siliconcompiler/schema/journalingschema.py +0 -242
  94. siliconcompiler/tools/yosys/procs.tcl +0 -71
  95. siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +0 -68
  96. siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +0 -68
  97. siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +0 -68
  98. /siliconcompiler/tools/yosys/{sc_lec.tcl → scripts/sc_lec.tcl} +0 -0
  99. /siliconcompiler/tools/yosys/{sc_screenshot.tcl → scripts/sc_screenshot.tcl} +0 -0
  100. /siliconcompiler/tools/yosys/{syn_strategies.tcl → scripts/syn_strategies.tcl} +0 -0
  101. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/WHEEL +0 -0
  102. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/entry_points.txt +0 -0
  103. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/licenses/LICENSE +0 -0
  104. {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 class are mutually exclusive.
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
- return param.get(field, step=step, index=index)
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
- return param.set(value, field=field, clobber=clobber, step=step, index=index)
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
- return param.add(value, field=field, step=step, index=index)
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:], include_default=include_default)
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
- manifest["default"] = self.__default.getdict(include_default=include_default)
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
- manifest[key] = item.getdict(include_default=include_default)
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
- return copy.deepcopy(self)
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
- return self.__name
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 not self.__name and key and key[-1] != "default":
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,