gammasimtools 0.5.1__py3-none-any.whl → 0.6.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 (78) hide show
  1. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/METADATA +80 -28
  2. gammasimtools-0.6.1.dist-info/RECORD +91 -0
  3. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/entry_points.txt +4 -2
  5. simtools/_version.py +14 -2
  6. simtools/applications/add_file_to_db.py +2 -1
  7. simtools/applications/compare_cumulative_psf.py +10 -15
  8. simtools/applications/db_development_tools/add_new_parameter_to_db.py +12 -6
  9. simtools/applications/derive_mirror_rnda.py +95 -71
  10. simtools/applications/generate_corsika_histograms.py +216 -131
  11. simtools/applications/generate_default_metadata.py +110 -0
  12. simtools/applications/generate_simtel_array_histograms.py +192 -0
  13. simtools/applications/get_file_from_db.py +1 -1
  14. simtools/applications/get_parameter.py +3 -3
  15. simtools/applications/make_regular_arrays.py +89 -93
  16. simtools/applications/{plot_layout_array.py → plot_array_layout.py} +15 -14
  17. simtools/applications/print_array_elements.py +81 -34
  18. simtools/applications/produce_array_config.py +2 -2
  19. simtools/applications/production.py +39 -5
  20. simtools/applications/sim_showers_for_trigger_rates.py +26 -30
  21. simtools/applications/simulate_prod.py +49 -107
  22. simtools/applications/submit_data_from_external.py +8 -10
  23. simtools/applications/tune_psf.py +16 -18
  24. simtools/applications/validate_camera_efficiency.py +63 -9
  25. simtools/applications/validate_camera_fov.py +9 -13
  26. simtools/applications/validate_file_using_schema.py +127 -0
  27. simtools/applications/validate_optics.py +13 -15
  28. simtools/camera_efficiency.py +73 -80
  29. simtools/configuration/commandline_parser.py +52 -22
  30. simtools/configuration/configurator.py +98 -33
  31. simtools/constants.py +9 -0
  32. simtools/corsika/corsika_config.py +28 -22
  33. simtools/corsika/corsika_default_config.py +282 -0
  34. simtools/corsika/corsika_histograms.py +328 -282
  35. simtools/corsika/corsika_histograms_visualize.py +162 -163
  36. simtools/corsika/corsika_runner.py +8 -4
  37. simtools/corsika_simtel/corsika_simtel_runner.py +18 -23
  38. simtools/data_model/data_reader.py +129 -0
  39. simtools/data_model/metadata_collector.py +346 -118
  40. simtools/data_model/metadata_model.py +123 -218
  41. simtools/data_model/model_data_writer.py +79 -22
  42. simtools/data_model/validate_data.py +96 -46
  43. simtools/db_handler.py +67 -42
  44. simtools/io_operations/__init__.py +0 -0
  45. simtools/io_operations/hdf5_handler.py +112 -0
  46. simtools/{io_handler.py → io_operations/io_handler.py} +51 -22
  47. simtools/job_execution/job_manager.py +1 -1
  48. simtools/layout/{layout_array.py → array_layout.py} +168 -199
  49. simtools/layout/geo_coordinates.py +196 -0
  50. simtools/layout/telescope_position.py +12 -12
  51. simtools/model/array_model.py +16 -14
  52. simtools/model/camera.py +5 -8
  53. simtools/model/mirrors.py +136 -73
  54. simtools/model/model_utils.py +1 -69
  55. simtools/model/telescope_model.py +32 -25
  56. simtools/psf_analysis.py +26 -19
  57. simtools/ray_tracing.py +54 -26
  58. simtools/schemas/data.metaschema.yml +400 -0
  59. simtools/schemas/metadata.metaschema.yml +566 -0
  60. simtools/simtel/simtel_config_writer.py +14 -5
  61. simtools/simtel/simtel_histograms.py +266 -83
  62. simtools/simtel/simtel_runner.py +8 -7
  63. simtools/simtel/simtel_runner_array.py +7 -8
  64. simtools/simtel/simtel_runner_camera_efficiency.py +48 -2
  65. simtools/simtel/simtel_runner_ray_tracing.py +61 -25
  66. simtools/simulator.py +43 -50
  67. simtools/utils/general.py +232 -286
  68. simtools/utils/geometry.py +163 -0
  69. simtools/utils/names.py +294 -142
  70. simtools/visualization/legend_handlers.py +115 -9
  71. simtools/visualization/visualize.py +13 -13
  72. gammasimtools-0.5.1.dist-info/RECORD +0 -83
  73. simtools/applications/plot_simtel_histograms.py +0 -120
  74. simtools/applications/validate_schema_files.py +0 -135
  75. simtools/corsika/corsika_output_visualize.py +0 -345
  76. simtools/data_model/validate_schema.py +0 -285
  77. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/LICENSE +0 -0
  78. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/top_level.txt +0 -0
@@ -1,30 +1,46 @@
1
+ """
2
+ Metadata collector for simtools.
3
+
4
+ This should be the only module in simtools with knowledge on the
5
+ implementation of the metadata model.
6
+
7
+ """
1
8
  import datetime
9
+ import getpass
2
10
  import logging
3
11
  from pathlib import Path
4
12
 
13
+ from astropy.table import Table
14
+
15
+ import simtools.constants
5
16
  import simtools.utils.general as gen
6
17
  import simtools.version
7
- from simtools import io_handler
8
- from simtools.data_model import metadata_model, validate_schema
9
- from simtools.utils import names
18
+ from simtools.data_model import metadata_model
19
+ from simtools.io_operations import io_handler
10
20
 
11
21
  __all__ = ["MetadataCollector"]
12
22
 
13
23
 
14
24
  class MetadataCollector:
15
25
  """
16
- Collects and combines metadata associated with the current activity
17
- (e.g., the execution of an application).
18
- Follows CTAO top-level metadata definition.
26
+ Collects and combines metadata associated to describe the current
27
+ simtools activity and its data products. Collect as much metadata
28
+ as possible from command line configuration, input data, environment,
29
+ schema descriptions.
30
+ Depends on the CTAO top-level metadata definition.
19
31
 
20
32
  Parameters
21
33
  ----------
22
- args: argparse.Namespace
34
+ args_dict: dict
23
35
  Command line parameters
36
+ metadata_file_name: str
37
+ Name of metadata file (only required when args_dict is None)
38
+ data_model_name: str
39
+ Name of data model parameter
24
40
 
25
41
  """
26
42
 
27
- def __init__(self, args_dict):
43
+ def __init__(self, args_dict, metadata_file_name=None, data_model_name=None):
28
44
  """
29
45
  Initialize metadata collector.
30
46
 
@@ -33,38 +49,128 @@ class MetadataCollector:
33
49
  self._logger = logging.getLogger(__name__)
34
50
  self.io_handler = io_handler.IOHandler()
35
51
 
36
- self.args_dict = args_dict
52
+ self.args_dict = args_dict if args_dict else {}
53
+ self.data_model_name = data_model_name
54
+ self.schema_file = None
55
+ self.schema_dict = None
37
56
  self.top_level_meta = gen.change_dict_keys_case(
38
- data_dict=metadata_model.top_level_reference_schema(), lower_case=True
57
+ data_dict=metadata_model.get_default_metadata_dict(), lower_case=True
39
58
  )
40
- self.collect_product_meta_data()
59
+ self.input_metadata = self._read_input_metadata_from_file(
60
+ metadata_file_name=metadata_file_name
61
+ )
62
+ self.collect_meta_data()
41
63
 
42
- def collect_product_meta_data(self):
64
+ def collect_meta_data(self):
43
65
  """
44
66
  Collect and verify product metadata from different sources.
45
67
 
46
68
  """
47
69
 
48
- self._fill_association_meta_from_args(
49
- self.top_level_meta["cta"]["context"]["sim"]["association"]
70
+ self._fill_contact_meta(self.top_level_meta["cta"]["contact"])
71
+ self._fill_product_meta(self.top_level_meta["cta"]["product"])
72
+ self._fill_activity_meta(self.top_level_meta["cta"]["activity"])
73
+ self._fill_process_meta(self.top_level_meta["cta"]["process"])
74
+ self._fill_context_from_input_meta(self.top_level_meta["cta"]["context"])
75
+ self._fill_associated_elements_from_args(
76
+ self.top_level_meta["cta"]["context"]["associated_elements"]
50
77
  )
51
78
 
52
- self._fill_product_meta(self.top_level_meta["cta"]["product"])
79
+ def get_data_model_schema_file_name(self):
80
+ """
81
+ Return data model schema file name.
82
+ The schema file name is taken (in this order) from the command line,
83
+ from the metadata file, from the data model name, or from the input
84
+ metadata file.
85
+
86
+ Returns
87
+ -------
88
+ str
89
+ Name of schema file.
53
90
 
54
- self._fill_top_level_meta_from_file(self.top_level_meta["cta"])
91
+ """
92
+
93
+ # from command line
94
+ try:
95
+ if self.args_dict["schema"]:
96
+ self._logger.debug(f"Schema file from command line: {self.args_dict['schema']}")
97
+ return self.args_dict["schema"]
98
+ except KeyError:
99
+ pass
55
100
 
56
- self._fill_association_id(self.top_level_meta["cta"]["context"]["sim"]["association"])
101
+ # from metadata
102
+ try:
103
+ if self.top_level_meta["cta"]["product"]["data"]["model"]["url"]:
104
+ self._logger.debug(
105
+ "Schema file from product metadata: "
106
+ f"{self.top_level_meta['cta']['product']['data']['model']['url']}"
107
+ )
108
+ return self.top_level_meta["cta"]["product"]["data"]["model"]["url"]
109
+ except KeyError:
110
+ pass
57
111
 
58
- self._fill_activity_meta(self.top_level_meta["cta"]["activity"])
112
+ # from data model name
113
+ if self.data_model_name:
114
+ self._logger.debug(f"Schema file from data model name: {self.data_model_name}")
115
+ return f"{simtools.constants.SCHEMA_URL}{self.data_model_name}.schema.yml"
116
+
117
+ # from input metadata
118
+ try:
119
+ self._logger.debug(
120
+ "Schema file from input metadata: "
121
+ f"{self.input_metadata['cta']['product']['data']['model']['url']}"
122
+ )
123
+ return self.input_metadata["cta"]["product"]["data"]["model"]["url"]
124
+ except KeyError:
125
+ pass
126
+
127
+ self._logger.warning("No schema file found.")
128
+ return None
129
+
130
+ def get_data_model_schema_dict(self):
131
+ """
132
+ Return data model schema dictionary.
133
+
134
+ Returns
135
+ -------
136
+ dict
137
+ Data model schema dictionary.
138
+
139
+ """
140
+
141
+ try:
142
+ return gen.collect_data_from_file_or_dict(file_name=self.schema_file, in_dict=None)
143
+ except gen.InvalidConfigData:
144
+ self._logger.debug(f"No valid schema file provided ({self.schema_file}).")
145
+ return {}
146
+
147
+ def _fill_contact_meta(self, contact_dict):
148
+ """
149
+ Fill contact metadata fields.
150
+
151
+ Parameters
152
+ ----------
153
+ contact_dict: dict
154
+ Dictionary for contact metadata fields.
59
155
 
60
- def _fill_association_meta_from_args(self, association_dict):
156
+ """
157
+
158
+ if contact_dict.get("name", None) is None:
159
+ contact_dict["name"] = getpass.getuser()
160
+
161
+ def _fill_associated_elements_from_args(self, associated_elements_dict):
61
162
  """
62
163
  Append association metadata set through configurator.
63
164
 
165
+ Note
166
+ ----
167
+ This function might go in future, as instrument
168
+ information will not be given via command line.
169
+
64
170
  Parameters
65
171
  ----------
66
- association_dict: dict
67
- Dictionary for assocation metadata field.
172
+ associated_elements_dict: dict
173
+ Dictionary for associated elements field.
68
174
 
69
175
  Raises
70
176
  ------
@@ -90,16 +196,16 @@ class MetadataCollector:
90
196
  self._logger.error("Error reading association metadata from args")
91
197
  raise
92
198
 
93
- self._fill_context_sim_list(association_dict, _association)
199
+ self._fill_context_sim_list(associated_elements_dict, _association)
94
200
 
95
- def _fill_top_level_meta_from_file(self, top_level_dict):
201
+ def _fill_context_from_input_meta(self, context_dict):
96
202
  """
97
- Read and validate metadata from file. Fill metadata into top-level template.
203
+ Read and validate input metadata from file and fill CONTEXT metadata fields.
98
204
 
99
205
  Parameters
100
206
  ----------
101
- top_level_dict: dict
102
- Dictionary for top level metadata.
207
+ context_dict: dict
208
+ Dictionary with context level metadata.
103
209
 
104
210
  Raises
105
211
  ------
@@ -108,26 +214,99 @@ class MetadataCollector:
108
214
 
109
215
  """
110
216
 
111
- if self.args_dict.get("input_meta", None) is None:
112
- self._logger.debug("Skipping metadata reading; no metadata file defined.")
113
- return
114
-
115
- _schema_validator = validate_schema.SchemaValidator()
116
- _input_meta = _schema_validator.validate_and_transform(
117
- meta_file_name=self.args_dict["input_meta"],
118
- )
217
+ try:
218
+ self._merge_config_dicts(context_dict, self.input_metadata["cta"]["context"])
219
+ for key in ("document", "associated_elements", "associated_data"):
220
+ self._copy_list_type_metadata(context_dict, self.input_metadata["cta"], key)
221
+ except KeyError:
222
+ self._logger.debug("No context metadata defined in input metadata file.")
119
223
 
120
- self._merge_config_dicts(top_level_dict, _input_meta)
121
- # list entry copies
122
- for association in _input_meta["product"]["association"]:
224
+ try:
123
225
  self._fill_context_sim_list(
124
- top_level_dict["context"]["sim"]["association"], association
226
+ context_dict["associated_data"], self.input_metadata["cta"]["product"]
125
227
  )
228
+ except (KeyError, TypeError):
229
+ self._logger.debug("No input product metadata appended to associated data.")
230
+
231
+ def _read_input_metadata_from_file(self, metadata_file_name=None, observatory="CTA"):
232
+ """
233
+ Read and validate input metadata from file. In case of an ecsv file including a
234
+ table, the metadata is read from the table meta data. Returns empty dict in case
235
+ no file is given.
236
+
237
+ Parameter
238
+ ---------
239
+ metadata_file_name: str or Path
240
+ Name of metadata file.
241
+ observatory: str
242
+ Observatory name.
243
+
244
+ Returns
245
+ -------
246
+ dict
247
+ Metadata dictionary.
248
+
249
+ Raises
250
+ ------
251
+ gen.InvalidConfigData, FileNotFoundError
252
+ if metadata cannot be read from file.
253
+ KeyError:
254
+ if metadata does not exist for the given observatory.
255
+
256
+ """
257
+
126
258
  try:
127
- for document in _input_meta["product"]["document"]:
128
- self._fill_context_sim_list(top_level_dict["context"]["sim"]["document"], document)
129
- except KeyError:
130
- top_level_dict["context"]["sim"].pop("document")
259
+ metadata_file_name = (
260
+ self.args_dict.get("input_meta", None)
261
+ if metadata_file_name is None
262
+ else metadata_file_name
263
+ )
264
+ except TypeError:
265
+ pass
266
+
267
+ if metadata_file_name is None:
268
+ self._logger.debug("No input metadata file defined.")
269
+ return {}
270
+
271
+ # metadata from yml or json file
272
+ if Path(metadata_file_name).suffix in (".yaml", ".yml", ".json"):
273
+ try:
274
+ self._logger.debug("Reading meta data from %s", metadata_file_name)
275
+ _input_metadata = gen.collect_data_from_file_or_dict(
276
+ file_name=metadata_file_name, in_dict=None
277
+ )
278
+ _json_type_metadata = {"Metadata", "metadata", "METADATA"}.intersection(
279
+ _input_metadata
280
+ )
281
+ if len(_json_type_metadata) == 1:
282
+ _input_metadata = _input_metadata[_json_type_metadata.pop()]
283
+ elif len(_json_type_metadata) > 1:
284
+ self._logger.error(
285
+ "More than one metadata entry found in %s", metadata_file_name
286
+ )
287
+ raise gen.InvalidConfigData
288
+ except (gen.InvalidConfigData, FileNotFoundError):
289
+ self._logger.error("Failed reading metadata from %s", metadata_file_name)
290
+ raise
291
+ # metadata from table meta in ecsv file
292
+ elif Path(metadata_file_name).suffix == ".ecsv":
293
+ try:
294
+ _input_metadata = {observatory: Table.read(metadata_file_name).meta[observatory]}
295
+ except (FileNotFoundError, KeyError):
296
+ self._logger.error(
297
+ "Failed reading metadata for %s from %s", observatory, metadata_file_name
298
+ )
299
+ raise
300
+ else:
301
+ self._logger.error("Unknown metadata file format: %s", metadata_file_name)
302
+ raise gen.InvalidConfigData
303
+
304
+ metadata_model.validate_schema(_input_metadata, None)
305
+
306
+ return gen.change_dict_keys_case(
307
+ self._process_metadata_from_file(_input_metadata),
308
+ lower_case=True,
309
+ )
131
310
 
132
311
  def _fill_product_meta(self, product_dict):
133
312
  """
@@ -146,69 +325,43 @@ class MetadataCollector:
146
325
 
147
326
  """
148
327
 
328
+ self.schema_file = self.get_data_model_schema_file_name()
329
+ self.schema_dict = self.get_data_model_schema_dict()
330
+
149
331
  product_dict["id"] = self.args_dict.get("activity_id", "UNDEFINED_ACTIVITY_ID")
150
- self._logger.debug(f"Reading activitiy UUID {product_dict['id']}")
332
+ product_dict["creation_time"] = datetime.datetime.now().isoformat(timespec="seconds")
333
+ product_dict["description"] = self.schema_dict.get("description", None)
151
334
 
335
+ # DATA:CATEGORY
152
336
  product_dict["data"]["category"] = "SIM"
153
337
  product_dict["data"]["level"] = "R1"
154
- product_dict["data"]["type"] = "service"
338
+ product_dict["data"]["type"] = "Service"
339
+ try:
340
+ product_dict["data"]["association"] = self.schema_dict["instrument"]["class"]
341
+ except KeyError:
342
+ pass
343
+
344
+ # DATA:MODEL
345
+ helper_dict = {"name": "name", "version": "version", "type": "base_schema"}
346
+ for key, value in helper_dict.items():
347
+ product_dict["data"]["model"][key] = self.schema_dict.get(value, None)
348
+ product_dict["data"]["model"]["url"] = self.schema_file
155
349
 
156
- _schema_dict = self._collect_schema_dict()
157
- product_dict["data"]["model"]["name"] = _schema_dict.get("name", "simpipe-schema")
158
- product_dict["data"]["model"]["version"] = _schema_dict.get("version", "0.0.0")
159
350
  product_dict["format"] = self.args_dict.get("output_file_format", None)
160
351
  product_dict["filename"] = str(self.args_dict.get("output_file", None))
161
352
 
162
- def _collect_schema_dict(self):
353
+ def _fill_process_meta(self, process_dict):
163
354
  """
164
- Read schema from file.
165
-
166
- The schema configuration parameter points to a directory or a file.
167
- For the case of a directory, the schema file is assumed to be named
168
- <parameter_name>.schema.yml.
169
-
170
- Returns
171
- -------
172
- dict
173
- Dictionary containing schema metadata.
174
-
175
- """
176
-
177
- _schema = self.args_dict.get("schema", "")
178
- if Path(_schema).is_dir():
179
- try:
180
- _data_dict = gen.collect_data_from_yaml_or_dict(
181
- in_yaml=self.args_dict.get("input", None), in_dict=None, allow_empty=True
182
- )
183
- return gen.collect_dict_from_file(
184
- file_path=_schema,
185
- file_name=f"{_data_dict['name']}.schema.yml",
186
- )
187
- except (TypeError, KeyError):
188
- return {}
189
- return gen.collect_dict_from_file(_schema)
190
-
191
- @staticmethod
192
- def _fill_association_id(association_dict):
193
- """
194
- Fill association id from site and telescope class, type, subtype.
355
+ Fill process fields in metadata.
195
356
 
196
357
  Parameters
197
358
  ----------
198
- association_dict: dict
199
- Association dictionary.
359
+ process_dict: dict
360
+ Dictionary for process metadata fields.
200
361
 
201
362
  """
202
- for association in association_dict:
203
- try:
204
- association["id"] = names.simtools_instrument_name(
205
- site=association["site"],
206
- telescope_class_name=association["class"],
207
- sub_system_name=association["type"],
208
- telescope_id_name=association.get("subtype", "D"),
209
- )
210
- except ValueError:
211
- association["id"] = None
363
+
364
+ process_dict["type"] = "simulation"
212
365
 
213
366
  def _fill_activity_meta(self, activity_dict):
214
367
  """
@@ -217,11 +370,13 @@ class MetadataCollector:
217
370
  Parameters
218
371
  ----------
219
372
  activity_dict: dict
220
- Dictionary for top-level activitiy metadata.
373
+ Dictionary for top-level activity metadata.
221
374
 
222
375
  """
223
376
 
224
377
  activity_dict["name"] = self.args_dict.get("label", None)
378
+ activity_dict["type"] = "software"
379
+ activity_dict["id"] = self.args_dict.get("activity_id", "UNDEFINED_ACTIVITY_ID")
225
380
  activity_dict["start"] = datetime.datetime.now().isoformat(timespec="seconds")
226
381
  activity_dict["end"] = activity_dict["start"]
227
382
  activity_dict["software"]["name"] = "simtools"
@@ -265,47 +420,120 @@ class MetadataCollector:
265
420
  self._logger.error("Error merging dictionaries")
266
421
  raise
267
422
 
268
- def input_data_file_name(self):
423
+ def _fill_context_sim_list(self, meta_list, new_entry_dict):
269
424
  """
270
- Return input data file (full path).
425
+ Fill list-type entries into metadata. Take into account the first list entry is the default
426
+ value filled with Nones.
427
+
428
+ Parameters
429
+ ----------
430
+ meta_list: list
431
+ List of metadata entries.
432
+ new_entry_dict: dict
433
+ New metadata entry to be added to meta_list.
271
434
 
272
435
  Returns
273
436
  -------
274
- str
275
- Input data file (full path).
437
+ list
438
+ Updated meta list.
276
439
 
277
- Raises
278
- ------
279
- KeyError
280
- if missing description of INPUT_DATA
281
440
  """
282
441
 
442
+ if len(new_entry_dict) == 0:
443
+ return []
283
444
  try:
284
- return self.args_dict["input_data"]
285
- except KeyError:
286
- self._logger.error("Missing description of INPUT_DATA")
287
- raise
445
+ if self._all_values_none(meta_list[0]):
446
+ meta_list[0] = new_entry_dict
447
+ else:
448
+ meta_list.append(new_entry_dict)
449
+ except (TypeError, IndexError):
450
+ meta_list = [new_entry_dict]
451
+ return meta_list
452
+
453
+ def _process_metadata_from_file(self, meta_dict):
454
+ """
455
+ Process metadata from file to ensure compatibility with metadata model.
456
+ Changes keys to lower case and removes line feeds from description fields.
457
+
458
+ Parameters
459
+ ----------
460
+ meta_dict: dict
461
+ Input metadata dictionary.
462
+
463
+ Returns
464
+ -------
465
+ dict
466
+ Metadata dictionary.
467
+
468
+ """
469
+
470
+ meta_dict = gen.change_dict_keys_case(meta_dict, True)
471
+ try:
472
+ meta_dict["cta"]["product"]["description"] = self._remove_line_feed(
473
+ meta_dict["cta"]["product"]["description"]
474
+ )
475
+ except (KeyError, AttributeError):
476
+ pass
477
+
478
+ return meta_dict
288
479
 
289
480
  @staticmethod
290
- def _fill_context_sim_list(product_list, new_entry_dict):
481
+ def _remove_line_feed(string):
291
482
  """
292
- Fill list-type entries into metadata. Take into account the first list entry is the default
293
- value filled with Nones.
483
+ Remove all line feeds from a string
484
+
485
+ Parameters
486
+ ----------
487
+ str
488
+ input string
294
489
 
295
490
  Returns
296
491
  -------
297
- list
298
- Updated product list.
492
+ str
493
+ with line feeds removed
494
+ """
495
+
496
+ return string.replace("\n", " ").replace("\r", "").replace(" ", " ")
497
+
498
+ def _copy_list_type_metadata(self, context_dict, _input_metadata, key):
499
+ """
500
+ Copy list-type metadata from file.
501
+ Very fine tuned.
502
+
503
+ Parameters
504
+ ----------
505
+ context_dict: dict
506
+ Dictionary for top level metadata (context level)
507
+ _input_metadata: dict
508
+ Dictionary for metadata from file.
509
+ key: str
510
+ Key for metadata entry.
299
511
 
300
512
  """
301
513
 
302
- if len(new_entry_dict) == 0:
303
- return []
304
514
  try:
305
- if any(v is not None for v in product_list[0].values()):
306
- product_list.append(new_entry_dict)
307
- else:
308
- product_list[0] = new_entry_dict
309
- except (TypeError, IndexError):
310
- product_list = [new_entry_dict]
311
- return product_list
515
+ for document in _input_metadata["context"][key]:
516
+ self._fill_context_sim_list(context_dict[key], document)
517
+ except KeyError:
518
+ pass
519
+
520
+ def _all_values_none(self, input_dict):
521
+ """
522
+ Check recursively if all values in a dictionary are None.
523
+
524
+ Parameters
525
+ ----------
526
+ input_dict: dict
527
+ Input dictionary.
528
+
529
+ Returns
530
+ -------
531
+ bool
532
+ True if all entries are None, False otherwise.
533
+
534
+ """
535
+
536
+ if not isinstance(input_dict, dict):
537
+ return input_dict is None
538
+
539
+ return all(self._all_values_none(value) for value in input_dict.values())