sierra-research 1.3.6__py3-none-any.whl → 1.5.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.
Files changed (254) hide show
  1. sierra/__init__.py +3 -3
  2. sierra/core/__init__.py +3 -3
  3. sierra/core/batchroot.py +223 -0
  4. sierra/core/cmdline.py +681 -1057
  5. sierra/core/compare.py +11 -0
  6. sierra/core/config.py +96 -88
  7. sierra/core/engine.py +306 -0
  8. sierra/core/execenv.py +380 -0
  9. sierra/core/expdef.py +11 -0
  10. sierra/core/experiment/__init__.py +1 -0
  11. sierra/core/experiment/bindings.py +150 -101
  12. sierra/core/experiment/definition.py +414 -245
  13. sierra/core/experiment/spec.py +83 -85
  14. sierra/core/exproot.py +44 -0
  15. sierra/core/generators/__init__.py +10 -0
  16. sierra/core/generators/experiment.py +528 -0
  17. sierra/core/generators/generator_factory.py +138 -137
  18. sierra/core/graphs/__init__.py +23 -0
  19. sierra/core/graphs/bcbridge.py +94 -0
  20. sierra/core/graphs/heatmap.py +245 -324
  21. sierra/core/graphs/pathset.py +27 -0
  22. sierra/core/graphs/schema.py +77 -0
  23. sierra/core/graphs/stacked_line.py +341 -0
  24. sierra/core/graphs/summary_line.py +506 -0
  25. sierra/core/logging.py +3 -2
  26. sierra/core/models/__init__.py +3 -1
  27. sierra/core/models/info.py +19 -0
  28. sierra/core/models/interface.py +52 -122
  29. sierra/core/pipeline/__init__.py +2 -5
  30. sierra/core/pipeline/pipeline.py +228 -126
  31. sierra/core/pipeline/stage1/__init__.py +10 -0
  32. sierra/core/pipeline/stage1/pipeline_stage1.py +45 -31
  33. sierra/core/pipeline/stage2/__init__.py +10 -0
  34. sierra/core/pipeline/stage2/pipeline_stage2.py +8 -11
  35. sierra/core/pipeline/stage2/runner.py +401 -0
  36. sierra/core/pipeline/stage3/__init__.py +12 -0
  37. sierra/core/pipeline/stage3/gather.py +321 -0
  38. sierra/core/pipeline/stage3/pipeline_stage3.py +37 -84
  39. sierra/core/pipeline/stage4/__init__.py +12 -2
  40. sierra/core/pipeline/stage4/pipeline_stage4.py +36 -354
  41. sierra/core/pipeline/stage5/__init__.py +12 -0
  42. sierra/core/pipeline/stage5/pipeline_stage5.py +33 -208
  43. sierra/core/pipeline/yaml.py +48 -0
  44. sierra/core/plugin.py +529 -62
  45. sierra/core/proc.py +11 -0
  46. sierra/core/prod.py +11 -0
  47. sierra/core/ros1/__init__.py +5 -1
  48. sierra/core/ros1/callbacks.py +22 -21
  49. sierra/core/ros1/cmdline.py +59 -88
  50. sierra/core/ros1/generators.py +159 -175
  51. sierra/core/ros1/variables/__init__.py +3 -0
  52. sierra/core/ros1/variables/exp_setup.py +122 -116
  53. sierra/core/startup.py +106 -76
  54. sierra/core/stat_kernels.py +4 -5
  55. sierra/core/storage.py +13 -32
  56. sierra/core/trampoline.py +30 -0
  57. sierra/core/types.py +116 -71
  58. sierra/core/utils.py +103 -106
  59. sierra/core/variables/__init__.py +1 -1
  60. sierra/core/variables/base_variable.py +12 -17
  61. sierra/core/variables/batch_criteria.py +387 -481
  62. sierra/core/variables/builtin.py +135 -0
  63. sierra/core/variables/exp_setup.py +19 -39
  64. sierra/core/variables/population_size.py +72 -76
  65. sierra/core/variables/variable_density.py +44 -68
  66. sierra/core/vector.py +1 -1
  67. sierra/main.py +256 -88
  68. sierra/plugins/__init__.py +119 -0
  69. sierra/plugins/compare/__init__.py +14 -0
  70. sierra/plugins/compare/graphs/__init__.py +19 -0
  71. sierra/plugins/compare/graphs/cmdline.py +120 -0
  72. sierra/plugins/compare/graphs/comparator.py +291 -0
  73. sierra/plugins/compare/graphs/inter_controller.py +531 -0
  74. sierra/plugins/compare/graphs/inter_scenario.py +297 -0
  75. sierra/plugins/compare/graphs/namecalc.py +53 -0
  76. sierra/plugins/compare/graphs/outputroot.py +73 -0
  77. sierra/plugins/compare/graphs/plugin.py +147 -0
  78. sierra/plugins/compare/graphs/preprocess.py +172 -0
  79. sierra/plugins/compare/graphs/schema.py +37 -0
  80. sierra/plugins/engine/__init__.py +14 -0
  81. sierra/plugins/engine/argos/__init__.py +18 -0
  82. sierra/plugins/{platform → engine}/argos/cmdline.py +144 -151
  83. sierra/plugins/{platform/argos/variables → engine/argos/generators}/__init__.py +5 -0
  84. sierra/plugins/engine/argos/generators/engine.py +394 -0
  85. sierra/plugins/engine/argos/plugin.py +393 -0
  86. sierra/plugins/{platform/argos/generators → engine/argos/variables}/__init__.py +5 -0
  87. sierra/plugins/engine/argos/variables/arena_shape.py +183 -0
  88. sierra/plugins/engine/argos/variables/cameras.py +240 -0
  89. sierra/plugins/engine/argos/variables/constant_density.py +112 -0
  90. sierra/plugins/engine/argos/variables/exp_setup.py +82 -0
  91. sierra/plugins/{platform → engine}/argos/variables/physics_engines.py +83 -87
  92. sierra/plugins/engine/argos/variables/population_constant_density.py +178 -0
  93. sierra/plugins/engine/argos/variables/population_size.py +115 -0
  94. sierra/plugins/engine/argos/variables/population_variable_density.py +123 -0
  95. sierra/plugins/engine/argos/variables/rendering.py +108 -0
  96. sierra/plugins/engine/ros1gazebo/__init__.py +18 -0
  97. sierra/plugins/engine/ros1gazebo/cmdline.py +175 -0
  98. sierra/plugins/{platform/ros1robot → engine/ros1gazebo}/generators/__init__.py +5 -0
  99. sierra/plugins/engine/ros1gazebo/generators/engine.py +125 -0
  100. sierra/plugins/engine/ros1gazebo/plugin.py +404 -0
  101. sierra/plugins/engine/ros1gazebo/variables/__init__.py +15 -0
  102. sierra/plugins/engine/ros1gazebo/variables/population_size.py +214 -0
  103. sierra/plugins/engine/ros1robot/__init__.py +18 -0
  104. sierra/plugins/engine/ros1robot/cmdline.py +159 -0
  105. sierra/plugins/{platform/ros1gazebo → engine/ros1robot}/generators/__init__.py +4 -0
  106. sierra/plugins/engine/ros1robot/generators/engine.py +95 -0
  107. sierra/plugins/engine/ros1robot/plugin.py +410 -0
  108. sierra/plugins/{hpc/local → engine/ros1robot/variables}/__init__.py +5 -0
  109. sierra/plugins/engine/ros1robot/variables/population_size.py +146 -0
  110. sierra/plugins/execenv/__init__.py +11 -0
  111. sierra/plugins/execenv/hpc/__init__.py +18 -0
  112. sierra/plugins/execenv/hpc/adhoc/__init__.py +18 -0
  113. sierra/plugins/execenv/hpc/adhoc/cmdline.py +30 -0
  114. sierra/plugins/execenv/hpc/adhoc/plugin.py +131 -0
  115. sierra/plugins/execenv/hpc/cmdline.py +137 -0
  116. sierra/plugins/execenv/hpc/local/__init__.py +18 -0
  117. sierra/plugins/execenv/hpc/local/cmdline.py +31 -0
  118. sierra/plugins/execenv/hpc/local/plugin.py +145 -0
  119. sierra/plugins/execenv/hpc/pbs/__init__.py +18 -0
  120. sierra/plugins/execenv/hpc/pbs/cmdline.py +30 -0
  121. sierra/plugins/execenv/hpc/pbs/plugin.py +121 -0
  122. sierra/plugins/execenv/hpc/slurm/__init__.py +18 -0
  123. sierra/plugins/execenv/hpc/slurm/cmdline.py +30 -0
  124. sierra/plugins/execenv/hpc/slurm/plugin.py +133 -0
  125. sierra/plugins/execenv/prefectserver/__init__.py +18 -0
  126. sierra/plugins/execenv/prefectserver/cmdline.py +66 -0
  127. sierra/plugins/execenv/prefectserver/dockerremote/__init__.py +18 -0
  128. sierra/plugins/execenv/prefectserver/dockerremote/cmdline.py +66 -0
  129. sierra/plugins/execenv/prefectserver/dockerremote/plugin.py +132 -0
  130. sierra/plugins/execenv/prefectserver/flow.py +66 -0
  131. sierra/plugins/execenv/prefectserver/local/__init__.py +18 -0
  132. sierra/plugins/execenv/prefectserver/local/cmdline.py +29 -0
  133. sierra/plugins/execenv/prefectserver/local/plugin.py +133 -0
  134. sierra/plugins/{hpc/adhoc → execenv/robot}/__init__.py +1 -0
  135. sierra/plugins/execenv/robot/turtlebot3/__init__.py +18 -0
  136. sierra/plugins/execenv/robot/turtlebot3/plugin.py +204 -0
  137. sierra/plugins/expdef/__init__.py +14 -0
  138. sierra/plugins/expdef/json/__init__.py +14 -0
  139. sierra/plugins/expdef/json/plugin.py +504 -0
  140. sierra/plugins/expdef/xml/__init__.py +14 -0
  141. sierra/plugins/expdef/xml/plugin.py +386 -0
  142. sierra/{core/hpc → plugins/proc}/__init__.py +1 -1
  143. sierra/plugins/proc/collate/__init__.py +15 -0
  144. sierra/plugins/proc/collate/cmdline.py +47 -0
  145. sierra/plugins/proc/collate/plugin.py +271 -0
  146. sierra/plugins/proc/compress/__init__.py +18 -0
  147. sierra/plugins/proc/compress/cmdline.py +47 -0
  148. sierra/plugins/proc/compress/plugin.py +123 -0
  149. sierra/plugins/proc/decompress/__init__.py +18 -0
  150. sierra/plugins/proc/decompress/plugin.py +96 -0
  151. sierra/plugins/proc/imagize/__init__.py +15 -0
  152. sierra/plugins/proc/imagize/cmdline.py +49 -0
  153. sierra/plugins/proc/imagize/plugin.py +270 -0
  154. sierra/plugins/proc/modelrunner/__init__.py +16 -0
  155. sierra/plugins/proc/modelrunner/plugin.py +250 -0
  156. sierra/plugins/proc/statistics/__init__.py +15 -0
  157. sierra/plugins/proc/statistics/cmdline.py +64 -0
  158. sierra/plugins/proc/statistics/plugin.py +390 -0
  159. sierra/plugins/{hpc → prod}/__init__.py +1 -0
  160. sierra/plugins/prod/graphs/__init__.py +18 -0
  161. sierra/plugins/prod/graphs/cmdline.py +269 -0
  162. sierra/plugins/prod/graphs/collate.py +279 -0
  163. sierra/plugins/prod/graphs/inter/__init__.py +13 -0
  164. sierra/plugins/prod/graphs/inter/generate.py +83 -0
  165. sierra/plugins/prod/graphs/inter/heatmap.py +86 -0
  166. sierra/plugins/prod/graphs/inter/line.py +134 -0
  167. sierra/plugins/prod/graphs/intra/__init__.py +15 -0
  168. sierra/plugins/prod/graphs/intra/generate.py +202 -0
  169. sierra/plugins/prod/graphs/intra/heatmap.py +74 -0
  170. sierra/plugins/prod/graphs/intra/line.py +114 -0
  171. sierra/plugins/prod/graphs/plugin.py +103 -0
  172. sierra/plugins/prod/graphs/targets.py +63 -0
  173. sierra/plugins/prod/render/__init__.py +18 -0
  174. sierra/plugins/prod/render/cmdline.py +72 -0
  175. sierra/plugins/prod/render/plugin.py +282 -0
  176. sierra/plugins/storage/__init__.py +5 -0
  177. sierra/plugins/storage/arrow/__init__.py +18 -0
  178. sierra/plugins/storage/arrow/plugin.py +38 -0
  179. sierra/plugins/storage/csv/__init__.py +9 -0
  180. sierra/plugins/storage/csv/plugin.py +12 -5
  181. sierra/version.py +3 -2
  182. sierra_research-1.5.0.dist-info/METADATA +238 -0
  183. sierra_research-1.5.0.dist-info/RECORD +186 -0
  184. {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info}/WHEEL +1 -2
  185. sierra/core/experiment/xml.py +0 -454
  186. sierra/core/generators/controller_generator_parser.py +0 -34
  187. sierra/core/generators/exp_creator.py +0 -351
  188. sierra/core/generators/exp_generators.py +0 -142
  189. sierra/core/graphs/scatterplot2D.py +0 -109
  190. sierra/core/graphs/stacked_line_graph.py +0 -249
  191. sierra/core/graphs/stacked_surface_graph.py +0 -220
  192. sierra/core/graphs/summary_line_graph.py +0 -369
  193. sierra/core/hpc/cmdline.py +0 -142
  194. sierra/core/models/graphs.py +0 -87
  195. sierra/core/pipeline/stage2/exp_runner.py +0 -286
  196. sierra/core/pipeline/stage3/imagizer.py +0 -149
  197. sierra/core/pipeline/stage3/run_collator.py +0 -317
  198. sierra/core/pipeline/stage3/statistics_calculator.py +0 -478
  199. sierra/core/pipeline/stage4/graph_collator.py +0 -319
  200. sierra/core/pipeline/stage4/inter_exp_graph_generator.py +0 -240
  201. sierra/core/pipeline/stage4/intra_exp_graph_generator.py +0 -317
  202. sierra/core/pipeline/stage4/model_runner.py +0 -168
  203. sierra/core/pipeline/stage4/rendering.py +0 -283
  204. sierra/core/pipeline/stage4/yaml_config_loader.py +0 -103
  205. sierra/core/pipeline/stage5/inter_scenario_comparator.py +0 -328
  206. sierra/core/pipeline/stage5/intra_scenario_comparator.py +0 -989
  207. sierra/core/platform.py +0 -493
  208. sierra/core/plugin_manager.py +0 -369
  209. sierra/core/root_dirpath_generator.py +0 -241
  210. sierra/plugins/hpc/adhoc/plugin.py +0 -125
  211. sierra/plugins/hpc/local/plugin.py +0 -81
  212. sierra/plugins/hpc/pbs/__init__.py +0 -9
  213. sierra/plugins/hpc/pbs/plugin.py +0 -126
  214. sierra/plugins/hpc/slurm/__init__.py +0 -9
  215. sierra/plugins/hpc/slurm/plugin.py +0 -130
  216. sierra/plugins/platform/__init__.py +0 -9
  217. sierra/plugins/platform/argos/__init__.py +0 -9
  218. sierra/plugins/platform/argos/generators/platform_generators.py +0 -383
  219. sierra/plugins/platform/argos/plugin.py +0 -337
  220. sierra/plugins/platform/argos/variables/arena_shape.py +0 -145
  221. sierra/plugins/platform/argos/variables/cameras.py +0 -243
  222. sierra/plugins/platform/argos/variables/constant_density.py +0 -136
  223. sierra/plugins/platform/argos/variables/exp_setup.py +0 -113
  224. sierra/plugins/platform/argos/variables/population_constant_density.py +0 -175
  225. sierra/plugins/platform/argos/variables/population_size.py +0 -102
  226. sierra/plugins/platform/argos/variables/population_variable_density.py +0 -132
  227. sierra/plugins/platform/argos/variables/rendering.py +0 -104
  228. sierra/plugins/platform/ros1gazebo/__init__.py +0 -9
  229. sierra/plugins/platform/ros1gazebo/cmdline.py +0 -213
  230. sierra/plugins/platform/ros1gazebo/generators/platform_generators.py +0 -137
  231. sierra/plugins/platform/ros1gazebo/plugin.py +0 -335
  232. sierra/plugins/platform/ros1gazebo/variables/__init__.py +0 -10
  233. sierra/plugins/platform/ros1gazebo/variables/population_size.py +0 -204
  234. sierra/plugins/platform/ros1robot/__init__.py +0 -9
  235. sierra/plugins/platform/ros1robot/cmdline.py +0 -175
  236. sierra/plugins/platform/ros1robot/generators/platform_generators.py +0 -112
  237. sierra/plugins/platform/ros1robot/plugin.py +0 -373
  238. sierra/plugins/platform/ros1robot/variables/__init__.py +0 -10
  239. sierra/plugins/platform/ros1robot/variables/population_size.py +0 -146
  240. sierra/plugins/robot/__init__.py +0 -9
  241. sierra/plugins/robot/turtlebot3/__init__.py +0 -9
  242. sierra/plugins/robot/turtlebot3/plugin.py +0 -194
  243. sierra_research-1.3.6.data/data/share/man/man1/sierra-cli.1 +0 -2349
  244. sierra_research-1.3.6.data/data/share/man/man7/sierra-examples.7 +0 -488
  245. sierra_research-1.3.6.data/data/share/man/man7/sierra-exec-envs.7 +0 -331
  246. sierra_research-1.3.6.data/data/share/man/man7/sierra-glossary.7 +0 -285
  247. sierra_research-1.3.6.data/data/share/man/man7/sierra-platforms.7 +0 -358
  248. sierra_research-1.3.6.data/data/share/man/man7/sierra-usage.7 +0 -725
  249. sierra_research-1.3.6.data/data/share/man/man7/sierra.7 +0 -78
  250. sierra_research-1.3.6.dist-info/METADATA +0 -500
  251. sierra_research-1.3.6.dist-info/RECORD +0 -133
  252. sierra_research-1.3.6.dist-info/top_level.txt +0 -1
  253. {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info}/entry_points.txt +0 -0
  254. {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -3,92 +3,137 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
5
  """
6
- Functionality for reading, writing, and manipulating experiment definitions.
6
+ Functionality for reading, writing, etc., experiment definitions.
7
7
 
8
- Currently, experiments can be specified in:
9
-
10
- - XML
8
+ Format-specific bits handled at a lower level via plugin; this is generic
9
+ functionality for experiment definitions.
11
10
  """
12
11
 
13
12
  # Core packages
13
+ import pathlib
14
14
  import typing as tp
15
15
  import logging
16
- import xml.etree.ElementTree as ET
17
- import sys
18
- import pathlib
16
+ import pickle
19
17
 
20
18
  # 3rd party packages
19
+ import implements
21
20
 
22
21
  # Project packages
23
- from sierra.core.experiment import xml
24
22
  from sierra.core import types
25
23
 
26
24
 
27
- class XMLExpDef:
28
- """Read, write, and modify parsed XML files into experiment definitions.
25
+ class WriterConfig:
26
+ """Config for writing :class:`~sierra.core.experiment.definition.BaseExpDef`.
29
27
 
30
- Functionality includes single tag removal/addition, single attribute
31
- change/add/remove.
28
+ Different parts of the AST can be written to multiple files, as configured.
32
29
 
33
- Attributes:
30
+ The order of operations for the applying the config should be:
34
31
 
35
- input_filepath: The location of the XML file to process.
32
+ - Extraction of subtree
36
33
 
37
- writer: The configuration for how the XML data will be written.
38
- """
34
+ - Renaming subtree root
39
35
 
40
- def __init__(self,
41
- input_fpath: pathlib.Path,
42
- write_config: tp.Optional[xml.WriterConfig] = None) -> None:
36
+ - Adding new children
43
37
 
44
- self.write_config = write_config
45
- self.input_fpath = input_fpath
46
- self.tree = ET.parse(self.input_fpath)
47
- self.root = self.tree.getroot()
48
- self.tag_adds = xml.TagAddList()
49
- self.attr_chgs = xml.AttrChangeSet()
38
+ - Adding grafts
50
39
 
51
- self.logger = logging.getLogger(__name__)
40
+ Attributes: values: Dict with the following possible key, value pairs:
52
41
 
53
- if sys.version_info < (3, 9):
54
- self.logger.warning(("XML files written with python < 3.9 "
55
- "are not human readable"))
42
+ - ``src_parent`` - The parent of the root of the AST specifying a
43
+ sub-tree to write out as a child of ``new_children_parent``, or
44
+ ``None`` to write out entire AST. This key is required. If ``None``
45
+ omitted then then tree rooted at ``src_tag`` is written out.
46
+ Otherwise, the subtree rooted at ``<src_parent>/<src_tag>`` is written
47
+ out instead.
56
48
 
57
- def write_config_set(self, config: xml.WriterConfig) -> None:
58
- """Set the write config for the object.
49
+ - ``src_tag`` - Unique query path expression for the child element
50
+ within ``src_parent`` to write out; that is this tag is the root of
51
+ the sub-tree within the experiment definition to write out. This key
52
+ is required.
59
53
 
60
- Provided for cases in which the configuration is dependent on whether or
61
- not certain tags are present in the input file.
54
+ - ``rename_to`` - String to rename the root of the AST written out.
55
+ This key is optional, and should be processed *after* ``{src_parent,
56
+ src_tag}``.
62
57
 
63
- """
64
- self.write_config = config
58
+ - ``new_children_parent`` - Unique query path expression for the parent
59
+ element to create new child elements under via ``new_children``. This
60
+ key is optional; can be omitted or set to ``None``.
61
+
62
+ - ``new_children`` - Ordered List of
63
+ :class:`~sierra.core.experiment.definition.ElementAdd` objects to use
64
+ to create new child elements under ``new_children_parent``. Must form
65
+ a tree with a single root when added in order.
66
+
67
+ - ``opath_leaf`` - Additional bits added to whatever the opath file stem
68
+ that is set for the
69
+ :class:`~sierra.core.experiment.definition.BaseExpDef` instance. This
70
+ key is optional. Can be used to add an extension; this is helpful
71
+ because some engines require input files to have a certain
72
+ extension, and SIERRA strips out the extension passed to
73
+ ``--expdef-template`` used as the bases for creating experiments.
74
+
75
+ - ``child_grafts_parent`` - Unique query path expression for the parent
76
+ element to for grafting elements under via ``child_grafts``. This
77
+ path expression is *relative* to ``<src_tag>`` due to ordering. This
78
+ key is optional; can be omitted or set to ``None``.
79
+
80
+ - ``child_grafts`` - Additional bits of the current AST to add under
81
+ ``child_grafts_parent`` in the written out experiment definition,
82
+ specified as a list of query path strings. This key is optional.
83
+ """
84
+
85
+ def __init__(self, values: tp.List[dict]) -> None:
86
+ assert isinstance(values, list), "values must be a list of dicts"
87
+
88
+ self.values = values
89
+
90
+ def add(self, value: dict) -> None:
91
+ self.values.append(value)
92
+
93
+
94
+ class BaseExpDef(implements.Interface):
95
+ """Base class for experiment definitions."""
96
+
97
+ def __init__(
98
+ self, input_fpath: pathlib.Path, write_config: tp.Optional[WriterConfig] = None
99
+ ) -> None:
100
+ pass
65
101
 
66
102
  def write(self, base_opath: pathlib.Path) -> None:
67
- """Write the XML stored in the object to the filesystem.
103
+ """Write the definition stored in the object to the filesystem."""
104
+ raise NotImplementedError
105
+
106
+ def n_mods(self) -> tp.Tuple[int, int]:
107
+ """
108
+ Get the # (adds, changes) as a tuple.
109
+ """
110
+ raise NotImplementedError
68
111
 
112
+ def flatten(self, keys: tp.List[str]) -> None:
69
113
  """
70
- assert self.write_config is not None, \
71
- "Can't write without write config"
114
+ Replace the specified filepath attributes with their contents.
72
115
 
73
- writer = xml.Writer(self.tree)
74
- writer(self.write_config, base_opath)
116
+ Filepaths are interpreted relative to the directory in which the
117
+ original experiment definition template resides, and assumed to be
118
+ defined as such.
119
+ """
120
+ raise NotImplementedError
75
121
 
76
- def attr_get(self, path: str, attr: str):
122
+ def attr_get(self, path: str, attr: str) -> tp.Union[str, int, float, None]:
77
123
  """Retrieve the specified attribute of the element at the specified path.
78
124
 
79
125
  If it does not exist, None is returned.
80
126
 
81
127
  """
82
- el = self.root.find(path)
83
- if el is not None and attr in el.attrib:
84
- return el.attrib[attr]
85
- return None
86
-
87
- def attr_change(self,
88
- path: str,
89
- attr: str,
90
- value: str,
91
- noprint: bool = False) -> bool:
128
+ raise NotImplementedError
129
+
130
+ def attr_change(
131
+ self,
132
+ path: str,
133
+ attr: str,
134
+ value: tp.Union[str, int, float],
135
+ noprint: bool = False,
136
+ ) -> bool:
92
137
  """Change the specified attribute of the element at the specified path.
93
138
 
94
139
  Only the attribute of the *FIRST* element matching the specified path is
@@ -96,43 +141,24 @@ class XMLExpDef:
96
141
 
97
142
  Arguments:
98
143
 
99
- path: An XPath expression that for the element containing the
100
- attribute to change. The element must exist or an error will be
144
+ path: An expression uniquely identifying the element containing the
145
+ attribute to change. The element must exist or an error will be
101
146
  raised.
102
147
 
103
- attr: An XPath expression for the name of the attribute to change
104
- within the enclosing element.
148
+ attr: An expression uniquely identify the attribute to change within
149
+ the enclosing element.
105
150
 
106
151
  value: The value to set the attribute to.
107
-
108
152
  """
109
- el = self.root.find(path)
110
- if el is None:
111
- if not noprint:
112
- self.logger.warning("Node '%s' not found", path)
113
- return False
114
-
115
- if attr not in el.attrib:
116
- if not noprint:
117
- self.logger.warning("Attribute '%s' not found in in path '%s'",
118
- attr,
119
- path)
120
- return False
121
-
122
- el.attrib[attr] = value
123
- self.logger.trace("Modify attr: '%s/%s' = '%s'", # type: ignore
124
- path,
125
- attr,
126
- value)
127
-
128
- self.attr_chgs.add(xml.AttrChange(path, attr, value))
129
- return True
130
-
131
- def attr_add(self,
132
- path: str,
133
- attr: str,
134
- value: str,
135
- noprint: bool = False) -> bool:
153
+ raise NotImplementedError
154
+
155
+ def attr_add(
156
+ self,
157
+ path: str,
158
+ attr: str,
159
+ value: tp.Union[str, int, float],
160
+ noprint: bool = False,
161
+ ) -> bool:
136
162
  """Add the specified attribute to the element matching the specified path.
137
163
 
138
164
  Only the *FIRST* element matching the specified path searching from the
@@ -140,216 +166,359 @@ class XMLExpDef:
140
166
 
141
167
  Arguments:
142
168
 
143
- path: An XPath expression that for the element containing the
169
+ path: An expression uniquely identifying the element containing the
144
170
  attribute to add. The element must exist or an error will be
145
171
  raised.
146
172
 
147
- attr: An XPath expression for the name of the attribute to change
173
+ attr: An expression uniquely identifying the attribute to change
148
174
  within the enclosing element.
149
175
 
150
176
  value: The value to set the attribute to.
151
177
 
152
178
  """
153
- el = self.root.find(path)
154
- if el is None:
155
- if not noprint:
156
- self.logger.warning("Node '%s' not found", path)
157
- return False
158
-
159
- if attr in el.attrib:
160
- if not noprint:
161
- self.logger.warning("Attribute '%s' already in path '%s'",
162
- attr,
163
- path)
164
- return False
165
-
166
- el.set(attr, value)
167
- self.logger.trace("Add new attribute: '%s/%s' = '%s'", # type: ignore
168
- path,
169
- attr,
170
- value)
171
- self.attr_chgs.add(xml.AttrChange(path, attr, value))
172
- return True
173
-
174
- def has_tag(self, path: str) -> bool:
175
- return self.root.find(path) is not None
179
+ raise NotImplementedError
180
+
181
+ def has_element(self, path: str) -> bool:
182
+ """Determine if the element uniquely identified by ``path`` exists."""
183
+ raise NotImplementedError
176
184
 
177
185
  def has_attr(self, path: str, attr: str) -> bool:
178
- el = self.root.find(path)
179
- if el is None:
180
- return False
181
- return attr in el.attrib
186
+ """Determine if the attribute uniquely identified by ``path`` exists."""
187
+ raise NotImplementedError
182
188
 
183
- def tag_change(self, path: str, tag: str, value: str) -> bool:
189
+ def element_change(self, path: str, tag: str, value: str) -> bool:
184
190
  """
185
191
  Change the specified tag of the element matching the specified path.
186
192
 
187
193
  Arguments:
188
194
 
189
- path: An XPath expression that for the element containing the tag to
190
- change. The element must exist or an error will be raised.
195
+ path: An expression uniquely identifying the element containing the
196
+ tag to change. The element must exist or an error will be
197
+ raised.
191
198
 
192
- tag: An XPath expression of the tag to change within the enclosing
193
- element.
199
+ tag: An expression uniquely identifying the tag to change within the
200
+ enclosing element.
194
201
 
195
202
  value: The value to set the tag to.
196
203
  """
197
- el = self.root.find(path)
198
- if el is None:
199
- self.logger.warning("Parent node '%s' not found", path)
200
- return False
201
-
202
- for child in el:
203
- if child.tag == tag:
204
- child.tag = value
205
- self.logger.trace("Modify tag: '%s/%s' = '%s'", # type: ignore
206
- path,
207
- tag,
208
- value)
209
- return True
210
-
211
- self.logger.warning("No such element '%s' found in '%s'", tag, path)
212
- return False
213
-
214
- def tag_remove(self, path: str, tag: str, noprint: bool = False) -> bool:
215
- """Remove the specified child in the enclosing parent specified by the path.
204
+ raise NotImplementedError
205
+
206
+ def element_remove(self, path: str, tag: str, noprint: bool = False) -> bool:
207
+ """Remove the specified child ``tag`` in the enclosing parent.
216
208
 
217
209
  If more than one tag matches, only one is removed. If the path does not
218
210
  exist, nothing is done.
219
211
 
220
212
  Arguments:
221
213
 
222
- path: An XPath expression that for the element containing the tag to
223
- remove. The element must exist or an error will be raised.
214
+ path: An expression uniquely identifying the element containing the
215
+ tag to remove. The element must exist or an error will be
216
+ raised.
224
217
 
225
- tag: An XPath expression of the tag to remove within the enclosing
226
- element.
218
+ tag: An expression uniquely identifying the tag to remove within the
219
+ enclosing element.
227
220
 
228
221
  """
222
+ raise NotImplementedError
229
223
 
230
- parent = self.root.find(path)
224
+ def element_remove_all(self, path: str, tag: str, noprint: bool = False) -> bool:
225
+ """Remove the specified child tag(s) in the enclosing parent.
231
226
 
232
- if parent is None:
233
- if not noprint:
234
- self.logger.warning("Parent node '%s' not found", path)
235
- return False
227
+ If more than one tag matches in the parent, all matching child tags are
228
+ removed.
236
229
 
237
- victim = parent.find(tag)
238
- if victim is None:
239
- if not noprint:
240
- self.logger.warning("No victim '%s' found in parent '%s'",
241
- tag,
242
- path)
243
- return False
230
+ Arguments:
244
231
 
245
- parent.remove(victim)
246
- return True
232
+ path: An expression uniquely identifying the element containing the
233
+ tag(s) to remove. The element must exist or an error will be
234
+ raised.
247
235
 
248
- def tag_remove_all(self,
249
- path: str,
250
- tag: str,
251
- noprint: bool = False) -> bool:
252
- """Remove the specified tag(s) in the enclosing parent specified by the path.
236
+ tag: An expression uniquely identifying the tag to remove within the
237
+ enclosing element.
253
238
 
254
- If more than one tag matches in the parent, all matching child tags are
255
- removed.
239
+ """
240
+ raise NotImplementedError
241
+
242
+ def element_add(
243
+ self,
244
+ path: str,
245
+ tag: str,
246
+ attr: tp.Optional[types.StrDict] = None,
247
+ allow_dup: bool = True,
248
+ noprint: bool = False,
249
+ ) -> bool:
250
+ """
251
+ Add tag name as a child element of enclosing parent.
252
+ """
253
+ raise NotImplementedError
254
+
255
+
256
+ class AttrChange:
257
+ """
258
+ Specification for a change to an existing expdef attribute.
259
+ """
260
+
261
+ def __init__(self, path: str, attr: str, value: tp.Union[str, int, float]) -> None:
262
+ self.path = path
263
+ self.attr = attr
264
+ self.value = value
265
+
266
+ def __iter__(self):
267
+ yield from [self.path, self.attr, self.value]
268
+
269
+ def __repr__(self) -> str:
270
+ return self.path + "/" + self.attr + ": " + str(self.value)
271
+
272
+
273
+ class NullMod:
274
+ """
275
+ Specification for a null-change (no change) to an existing expdef.
276
+ """
277
+
278
+ def __init__(self) -> None:
279
+ pass
280
+
281
+ def __iter__(self):
282
+ yield from []
283
+
284
+
285
+ class ElementRm:
286
+ """
287
+ Specification for removal of an existing expdef tag.
288
+ """
289
+
290
+ def __init__(self, path: str, tag: str):
291
+ """
292
+ Init the object.
256
293
 
257
294
  Arguments:
258
295
 
259
- path: An XPath expression that for the element containing the tag to
260
- remove. The element must exist or an error will be raised.
296
+ path: The path to the **parent** of the tag you want to remove, in
297
+ relevant syntax.
298
+
299
+ tag: The name of the tag to remove.
300
+ """
301
+ self.path = path
302
+ self.tag = tag
303
+
304
+ def __iter__(self):
305
+ yield from [self.path, self.tag]
306
+
307
+ def __repr__(self) -> str:
308
+ return self.path + "/" + self.tag
309
+
261
310
 
262
- tag: An XPath expression for the tag to remove within the enclosing
263
- element.
311
+ class ElementAdd:
312
+ """
313
+ Specification for adding a new expdef tag.
314
+
315
+ The tag may be added idempotently, or duplicates can be allowed.
316
+ """
264
317
 
318
+ @staticmethod
319
+ def as_root(tag: str, attr: types.StrDict) -> "ElementAdd":
320
+ return ElementAdd("", tag, attr, False, True)
321
+
322
+ def __init__(
323
+ self,
324
+ path: str,
325
+ tag: str,
326
+ attr: types.StrDict,
327
+ allow_dup: bool,
328
+ as_root: bool = False,
329
+ ):
265
330
  """
331
+ Init the object.
332
+
333
+ Arguments:
334
+
335
+ path: The path to the **parent** tag you want to add a new tag
336
+ under, in appropriate syntax. If None, then the tag will be
337
+ added as the root tag.
266
338
 
267
- parent = self.root.find(path)
268
-
269
- if parent is None:
270
- if not noprint:
271
- self.logger.warning("Parent node '%s' not found", path)
272
- return False
273
-
274
- victims = parent.findall(tag)
275
- if not victims:
276
- if not noprint:
277
- self.logger.warning("No victim '%s' found in parent '%s'",
278
- tag,
279
- path)
280
- return False
281
-
282
- for victim in victims:
283
- parent.remove(victim)
284
- self.logger.trace("Remove matching tag: '%s/%s'", # type: ignore
285
- path,
286
- tag)
287
-
288
- return True
289
-
290
- def tag_add(self,
291
- path: str,
292
- tag: str,
293
- attr: types.StrDict = {},
294
- allow_dup: bool = True,
295
- noprint: bool = False) -> bool:
339
+ tag: The name of the tag to add.
340
+
341
+ attr: A dictionary of (attribute, value) pairs to also create as
342
+ children of the new tag when creating the new tag.
296
343
  """
297
- Add tag name as a child element of enclosing parent.
344
+
345
+ self.path = path
346
+ self.tag = tag
347
+ self.attr = attr
348
+ self.allow_dup = allow_dup
349
+ self.as_root_elt = as_root
350
+
351
+ def __iter__(self):
352
+ yield from [self.path, self.tag, self.attr]
353
+
354
+ def __repr__(self) -> str:
355
+ return self.path + "/" + self.tag + ": " + str(self.attr)
356
+
357
+
358
+ class AttrChangeSet:
359
+ """
360
+ Data structure for :class:`AttrChange` objects.
361
+
362
+ The order in which attributes are changed doesn't matter from the standpoint
363
+ of correctness (i.e., different orders won't cause crashes).
364
+
365
+ """
366
+
367
+ @staticmethod
368
+ def unpickle(fpath: pathlib.Path) -> "AttrChangeSet":
369
+ """Unpickle changes.
370
+
371
+ You don't know how many there are, so go until you get an exception.
372
+
298
373
  """
299
- parent = self.root.find(path)
300
-
301
- if parent is None:
302
- if not noprint:
303
- self.logger.warning("Parent node '%s' not found", path)
304
- return False
305
-
306
- if not allow_dup:
307
- if parent.find(tag) is not None:
308
- if not noprint:
309
- self.logger.warning("Child tag '%s' already in parent '%s'",
310
- tag,
311
- path)
312
- return False
313
-
314
- ET.SubElement(parent, tag, attrib=attr)
315
- self.logger.trace("Add new unique tag: '%s/%s' = '%s'", # type: ignore
316
- path,
317
- tag,
318
- str(attr))
319
- else:
320
- # Use ET.Element instead of ET.SubElement so that child nodes with
321
- # the same 'tag' don't overwrite each other.
322
- child = ET.Element(tag, attrib=attr)
323
- parent.append(child)
324
- self.logger.trace("Add new tag: '%s/%s' = '%s'", # type: ignore
325
- path,
326
- tag,
327
- str(attr))
328
-
329
- self.tag_adds.append(xml.TagAdd(path, tag, attr, allow_dup))
330
- return True
331
-
332
-
333
- def unpickle(fpath: pathlib.Path) -> tp.Optional[tp.Union[xml.AttrChangeSet,
334
- xml.TagAddList]]:
335
- """Unickle all XML modifications from the pickle file at the path.
336
-
337
- You don't know how many there are, so go until you get an exception.
374
+ exp_def = AttrChangeSet()
375
+
376
+ try:
377
+ with open(fpath, "rb") as f:
378
+ while True:
379
+ exp_def |= AttrChangeSet(*pickle.load(f))
380
+ except EOFError:
381
+ pass
382
+ return exp_def
383
+
384
+ def __init__(self, *args: tp.Union[AttrChange, NullMod]) -> None:
385
+ self.changes = set(args)
386
+ self.logger = logging.getLogger(__name__)
387
+
388
+ def __len__(self) -> int:
389
+ return len(self.changes)
390
+
391
+ def __iter__(self) -> tp.Iterator[tp.Union[AttrChange, NullMod]]:
392
+ return iter(self.changes)
393
+
394
+ def __ior__(self, other: "AttrChangeSet") -> "AttrChangeSet":
395
+ self.changes |= other.changes
396
+ return self
338
397
 
398
+ def __or__(self, other: "AttrChangeSet") -> "AttrChangeSet":
399
+ new = AttrChangeSet(*self.changes)
400
+ new |= other
401
+ return new
402
+
403
+ def __repr__(self) -> str:
404
+ return str(self.changes)
405
+
406
+ def add(self, chg: AttrChange) -> None:
407
+ self.changes.add(chg)
408
+
409
+ def pickle(self, fpath: pathlib.Path, delete: bool = False) -> None:
410
+ from sierra.core import utils
411
+
412
+ if delete and utils.path_exists(fpath):
413
+ fpath.unlink()
414
+
415
+ with open(fpath, "ab") as f:
416
+ utils.pickle_dump(self.changes, f)
417
+
418
+
419
+ class ElementRmList:
339
420
  """
340
- try:
341
- return xml.AttrChangeSet.unpickle(fpath)
342
- except EOFError:
343
- pass
421
+ Data structure for :class:`ElementRm` objects.
344
422
 
345
- try:
346
- return xml.TagAddList.unpickle(fpath)
347
- except EOFError:
348
- pass
423
+ The order in which tags are removed matters (i.e., if you remove dependent
424
+ tags in the wrong order you will get an exception), hence the list
425
+ representation.
426
+
427
+ """
428
+
429
+ def __init__(self, *args: ElementRm) -> None:
430
+ self.rms = list(args)
431
+
432
+ def __len__(self) -> int:
433
+ return len(self.rms)
434
+
435
+ def __iter__(self) -> tp.Iterator[ElementRm]:
436
+ return iter(self.rms)
437
+
438
+ def __repr__(self) -> str:
439
+ return str(self.rms)
440
+
441
+ def extend(self, other: "ElementRmList") -> None:
442
+ self.rms.extend(other.rms)
443
+
444
+ def append(self, other: ElementRm) -> None:
445
+ self.rms.append(other)
446
+
447
+ def pickle(self, fpath: pathlib.Path, delete: bool = False) -> None:
448
+ from sierra.core import utils
449
+
450
+ if delete and utils.path_exists(fpath):
451
+ fpath.unlink()
452
+
453
+ with open(fpath, "ab") as f:
454
+ utils.pickle_dump(self.rms, f)
455
+
456
+
457
+ class ElementAddList:
458
+ """
459
+ Data structure for :class:`ElementAdd` objects.
460
+
461
+ The order in which tags are added matters (i.e., if you add dependent tags
462
+ in the wrong order you will get an exception), hence the list
463
+ representation.
464
+ """
465
+
466
+ @staticmethod
467
+ def unpickle(fpath: pathlib.Path) -> tp.Optional["ElementAddList"]:
468
+ """Unpickle modifications.
469
+
470
+ You don't know how many there are, so go until you get an exception.
471
+
472
+ """
473
+ exp_def = ElementAddList()
474
+
475
+ try:
476
+ with open(fpath, "rb") as f:
477
+ while True:
478
+ exp_def.append(*pickle.load(f))
479
+ except EOFError:
480
+ pass
481
+ return exp_def
482
+
483
+ def __init__(self, *args: ElementAdd) -> None:
484
+ self.adds = list(args)
485
+
486
+ def __len__(self) -> int:
487
+ return len(self.adds)
488
+
489
+ def __iter__(self) -> tp.Iterator[ElementAdd]:
490
+ return iter(self.adds)
491
+
492
+ def __repr__(self) -> str:
493
+ return str(self.adds)
494
+
495
+ def extend(self, other: "ElementAddList") -> None:
496
+ self.adds.extend(other.adds)
497
+
498
+ def append(self, other: ElementAdd) -> None:
499
+ self.adds.append(other)
500
+
501
+ def prepend(self, other: ElementAdd) -> None:
502
+ self.adds.insert(0, other)
503
+
504
+ def pickle(self, fpath: pathlib.Path, delete: bool = False) -> None:
505
+ from sierra.core import utils
506
+
507
+ if delete and utils.path_exists(fpath):
508
+ fpath.unlink()
349
509
 
350
- raise NotImplementedError
510
+ with open(fpath, "ab") as f:
511
+ utils.pickle_dump(self.adds, f)
351
512
 
352
513
 
353
- __api__ = [
354
- 'XMLExpDef',
514
+ __all__ = [
515
+ "BaseExpDef",
516
+ "WriterConfig",
517
+ "AttrChange",
518
+ "NullMod",
519
+ "AttrChangeSet",
520
+ "ElementAdd",
521
+ "ElementAddList",
522
+ "ElementRm",
523
+ "ElementRmList",
355
524
  ]