CMIP7-data-request-api 1.1.2__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 (36) hide show
  1. CMIP7_data_request_api-1.1.2.dist-info/LICENSE +21 -0
  2. CMIP7_data_request_api-1.1.2.dist-info/METADATA +210 -0
  3. CMIP7_data_request_api-1.1.2.dist-info/RECORD +36 -0
  4. CMIP7_data_request_api-1.1.2.dist-info/WHEEL +5 -0
  5. CMIP7_data_request_api-1.1.2.dist-info/entry_points.txt +2 -0
  6. CMIP7_data_request_api-1.1.2.dist-info/top_level.txt +1 -0
  7. data_request_api/__init__.py +1 -0
  8. data_request_api/command_line/__init__.py +0 -0
  9. data_request_api/command_line/export_dreq_lists_json.py +136 -0
  10. data_request_api/dev/JA/__init__.py +0 -0
  11. data_request_api/dev/JA/check_plev_requests.py +416 -0
  12. data_request_api/dev/JA/read_feedback_spreadsheet.py +141 -0
  13. data_request_api/dev/JA/workflow_example_GRtest.py +275 -0
  14. data_request_api/dev/JA/workflow_example_test.py +222 -0
  15. data_request_api/dev/MM/checksum.py +68 -0
  16. data_request_api/dev/MM/walking_data_request.ipynb +380 -0
  17. data_request_api/dev/MS/dreq_content_and_walking_data_request.ipynb +3755 -0
  18. data_request_api/dev/__init__.py +0 -0
  19. data_request_api/stable/__init__.py +0 -0
  20. data_request_api/stable/content/README.MD +106 -0
  21. data_request_api/stable/content/__init__.py +0 -0
  22. data_request_api/stable/content/dreq_api/__init__.py +0 -0
  23. data_request_api/stable/content/dreq_api/consolidate_export.py +488 -0
  24. data_request_api/stable/content/dreq_api/dreq_content.py +593 -0
  25. data_request_api/stable/content/dreq_api/mapping_table.py +335 -0
  26. data_request_api/stable/content/dreq_api/test_dreq_content.py +194 -0
  27. data_request_api/stable/content/dump_transformation.py +550 -0
  28. data_request_api/stable/query/__init__.py +0 -0
  29. data_request_api/stable/query/data_request.py +1120 -0
  30. data_request_api/stable/query/dreq_classes.py +372 -0
  31. data_request_api/stable/query/dreq_query.py +981 -0
  32. data_request_api/stable/query/vocabulary_server.py +208 -0
  33. data_request_api/stable/utilities/__init__.py +0 -0
  34. data_request_api/stable/utilities/logger.py +71 -0
  35. data_request_api/stable/utilities/tools.py +49 -0
  36. data_request_api/version.py +16 -0
@@ -0,0 +1,1120 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Data request.
6
+ """
7
+
8
+ from __future__ import division, print_function, unicode_literals, absolute_import
9
+
10
+ import argparse
11
+ import copy
12
+ import os
13
+ from collections import defaultdict
14
+
15
+ from data_request_api.stable.utilities.logger import get_logger, change_log_file, change_log_level
16
+ from data_request_api.stable.content.dump_transformation import transform_content
17
+ from data_request_api.stable.utilities.tools import read_json_file, write_csv_output_file_content
18
+ from data_request_api.stable.query.vocabulary_server import VocabularyServer, is_link_id_or_value, build_link_from_id
19
+
20
+ version = "1.0.1"
21
+
22
+
23
+ class ConstantValueObj(object):
24
+ """
25
+ Constant object which return the same value each time an attribute is asked.
26
+ It is used to avoid discrepancies between objects and strings.
27
+ """
28
+ def __init__(self, value="undef"):
29
+ self.value = value
30
+
31
+ def __getattr__(self, item):
32
+ return str(self)
33
+
34
+ def __str__(self):
35
+ return self.value
36
+
37
+ def __hash__(self):
38
+ return hash(self.value)
39
+
40
+ def __copy__(self):
41
+ return ConstantValueObj(self.value)
42
+
43
+ def __eq__(self, other):
44
+ return str(self) == str(other)
45
+
46
+ def __gt__(self, other):
47
+ return str(self) > str(other)
48
+
49
+ def __lt__(self, other):
50
+ return str(self) < str(other)
51
+
52
+ def __deepcopy__(self, memodict={}):
53
+ return self.__copy__()
54
+
55
+
56
+ class DRObjects(object):
57
+ """
58
+ Base object to build the ones used within the DR API.
59
+ Use to define basic information needed.
60
+ """
61
+ def __init__(self, id, dr, DR_type="undef", structure=dict(), **attributes):
62
+ """
63
+ Initialisation of the object.
64
+ :param str id: id of the object
65
+ :param DataRequest dr: reference data request object
66
+ :param str DR_type: type of DR object (for reference in vocabulary server)
67
+ :param dict structure: if needed, elements linked by structure to the current object
68
+ :param dict attributes: attributes of the object coming from vocabulary server
69
+ """
70
+ self.DR_type = DR_type
71
+ self.attributes = copy.deepcopy(attributes)
72
+ _, self.attributes["id"] = is_link_id_or_value(id)
73
+ self.structure = copy.deepcopy(structure)
74
+ self.dr = dr
75
+ self.attributes = self.transform_content(self.attributes, dr)
76
+ self.structure = self.transform_content(self.structure, dr, force_transform=True)
77
+
78
+ @staticmethod
79
+ def transform_content(input_dict, dr, force_transform=False):
80
+ """
81
+ Transform the input dict to have only elements which are object (either DRObject -for links- or
82
+ ConstantValueObj -for strings-).
83
+ :param dict input_dict: input dictionary to transform
84
+ :param DataRequest dr: reference Data Request to find elements from VS
85
+ :param bool force_transform: boolean indicating whether all elements should be considered as linked and
86
+ transform into DRObject (True) or alternatively to DRObject if link or ConstantValueObj if string.
87
+ :return dict: transformed dictionary
88
+ """
89
+ for (key, values) in input_dict.items():
90
+ if isinstance(values, list):
91
+ for (i, value) in enumerate(values):
92
+ if isinstance(value, str) and (force_transform or is_link_id_or_value(value)[0]):
93
+ input_dict[key][i] = dr.find_element(key, value)
94
+ elif isinstance(value, str):
95
+ input_dict[key][i] = ConstantValueObj(value)
96
+ elif isinstance(values, str) and (force_transform or is_link_id_or_value(values)[0]):
97
+ input_dict[key] = dr.find_element(key, values)
98
+ elif isinstance(values, str):
99
+ input_dict[key] = ConstantValueObj(values)
100
+ return input_dict
101
+
102
+ @classmethod
103
+ def from_input(cls, dr, id, DR_type="undef", elements=dict(), structure=dict()):
104
+ """
105
+ Create instance of the class using specific arguments.
106
+ :param DataRequest dr: reference Data Request objects
107
+ :param str id: id of the object
108
+ :param str DR_type: type of the object
109
+ :param dict elements: attributes of the objects (coming from VS)
110
+ :param dict structure: structure of the object through Data Request
111
+ :return: instance of the current class.
112
+ """
113
+ elements["id"] = id
114
+ return cls(dr=dr, DR_type=DR_type, structure=structure, **elements)
115
+
116
+ def __hash__(self):
117
+ return hash(self.id)
118
+
119
+ def __eq__(self, other):
120
+ return isinstance(other, type(self)) and self.id == other.id and self.DR_type == other.DR_type and \
121
+ self.structure == other.structure and self.attributes == other.attributes
122
+
123
+ def __lt__(self, other):
124
+ return isinstance(other, type(self)) and self.id < other.id
125
+
126
+ def __gt__(self, other):
127
+ return isinstance(other, type(self)) and self.id > other.id
128
+
129
+ def __copy__(self):
130
+ return type(self).__call__(dr=self.dr, DR_type=copy.deepcopy(self.DR_type),
131
+ structure=copy.deepcopy(self.structure), **copy.deepcopy(self.attributes))
132
+
133
+ def __deepcopy__(self, memodict={}):
134
+ return self.__copy__()
135
+
136
+ def check(self):
137
+ """
138
+ Make checks on the current object.
139
+ :return:
140
+ """
141
+ pass
142
+
143
+ def __str__(self):
144
+ return os.linesep.join(self.print_content())
145
+
146
+ def __repr__(self):
147
+ return os.linesep.join(self.print_content())
148
+
149
+ def __getattr__(self, item):
150
+ return self.attributes.get(item, ConstantValueObj())
151
+
152
+ def get(self, item):
153
+ return self.__getattr__(item)
154
+
155
+ def print_content(self, level=0, add_content=True):
156
+ """
157
+ Function to return a printable version of the content of the current class.
158
+ :param level: level of indent of the result
159
+ :param add_content: should inner content be added?
160
+ :return: a list of strings that can be assembled to print the content.
161
+ """
162
+ indent = " " * level
163
+ DR_type = copy.deepcopy(self.DR_type)
164
+ DR_type = self.dr.VS.to_singular(DR_type)
165
+ return [f"{indent}{DR_type}: {self.name} (id: {is_link_id_or_value(self.id)[1]})", ]
166
+
167
+ def filter_on_request(self, request_value):
168
+ """
169
+ Check whether the current object can be filtered by the requested value.
170
+ :param request_value: an object to be tested
171
+ :return bool, bool: a bool indicating whether the current object can be filtered by the requested one,
172
+ a bool indicating whether the current object is linked to the request one.
173
+ """
174
+ return request_value.DR_type == self.DR_type, request_value == self
175
+
176
+
177
+ class ExperimentsGroup(DRObjects):
178
+ def __init__(self, id, dr, DR_type="experiment_groups", structure=dict(experiments=list()), **attributes):
179
+ super().__init__(id=id, dr=dr, DR_type=DR_type, structure=structure, **attributes)
180
+
181
+ def check(self):
182
+ super().check()
183
+ logger = get_logger()
184
+ if self.count() == 0:
185
+ logger.critical(f"No experiment defined for {self.DR_type} id {self.id}")
186
+
187
+ def count(self):
188
+ """
189
+ Return the number of experiments linked to the ExperimentGroup
190
+ :return int: number of experiments of the ExperimentGroup
191
+ """
192
+ return len(self.get_experiments())
193
+
194
+ def get_experiments(self):
195
+ """
196
+ Return the list of experiments linked to the ExperimentGroup.
197
+ :return list of DRObjects: list of the experiments linked to the ExperimentGroup
198
+ """
199
+ return self.structure["experiments"]
200
+
201
+ def print_content(self, level=0, add_content=True):
202
+ rep = super().print_content(level=level)
203
+ if add_content:
204
+ indent = " " * (level + 1)
205
+ rep.append(f"{indent}Experiments included:")
206
+ for experiment in self.get_experiments():
207
+ rep.extend(experiment.print_content(level=level + 2))
208
+ return rep
209
+
210
+ @classmethod
211
+ def from_input(cls, dr, id, experiments=list(), **kwargs):
212
+ return super().from_input(DR_type="experiment_groups", dr=dr, id=id, structure=dict(experiments=experiments),
213
+ elements=kwargs)
214
+
215
+ def filter_on_request(self, request_value):
216
+ if request_value.DR_type in ["experiments", ]:
217
+ return True, request_value in self.get_experiments()
218
+ else:
219
+ return super().filter_on_request(request_value=request_value)
220
+
221
+
222
+ class Variable(DRObjects):
223
+ def __init__(self, id, dr, DR_type="variables", structure=dict(), **attributes):
224
+ super().__init__(id=id, dr=dr, DR_type=DR_type, structure=structure, **attributes)
225
+
226
+ @classmethod
227
+ def from_input(cls, dr, id, **kwargs):
228
+ return super().from_input(DR_type="variables", dr=dr, id=id, elements=kwargs, structure=dict())
229
+
230
+ def print_content(self, level=0, add_content=True):
231
+ """
232
+ Function to return a printable version of the content of the current class.
233
+ :param level: level of indent of the result
234
+ :param add_content: should inner content be added?
235
+ :return: a list of strings that can be assembled to print the content.
236
+ """
237
+ indent = " " * level
238
+ return [f"{indent}{self.DR_type.rstrip('s')}: {self.physical_parameter.name} at frequency {self.cmip7_frequency.name} (id: {is_link_id_or_value(self.id)[1]}, title: {self.title})", ]
239
+
240
+ def filter_on_request(self, request_value):
241
+ request_type = request_value.DR_type
242
+ if request_type in ["table_identifiers", ]:
243
+ return True, request_value in self.table
244
+ elif request_type in ["temporal_shape", ]:
245
+ return True, request_value == self.temporal_shape
246
+ elif request_type in ["spatial_shape", ]:
247
+ return True, request_value == self.spatial_shape
248
+ elif request_type in ["structure", ]:
249
+ return True, request_value == self.structure_title
250
+ elif request_type in ["physical_parameters", ]:
251
+ return True, request_value == self.physical_parameter
252
+ elif request_type in ["modelling_realm", ]:
253
+ return True, request_value in self.modelling_realm
254
+ elif request_type in ["esm-bcv", ]:
255
+ return True, request_value == self.esm_bcv
256
+ elif request_type in ["cf_standard_names", ]:
257
+ return True, request_value == self.cf_standard_name
258
+ elif request_type in ["cell_methods", ]:
259
+ return True, request_value == self.cell_methods
260
+ elif request_type in ["cell_measures", ]:
261
+ return True, request_value == self.cell_measures
262
+ else:
263
+ return super().filter_on_request(request_value)
264
+
265
+
266
+ class VariablesGroup(DRObjects):
267
+ def __init__(self, id, dr, DR_type="variable_groups",
268
+ structure=dict(variables=list(), mips=list(), priority_level="High"), **attributes):
269
+ super().__init__(id=id, dr=dr, DR_type=DR_type, structure=structure, **attributes)
270
+
271
+ def check(self):
272
+ super().check()
273
+ logger = get_logger()
274
+ if self.count() == 0:
275
+ logger.critical(f"No variable defined for {self.DR_type} id {self.id}")
276
+
277
+ @classmethod
278
+ def from_input(cls, dr, id, variables=list(), mips=list(), priority_level="High", **kwargs):
279
+ return super().from_input(DR_type="variable_groups", dr=dr, id=id, elements=kwargs,
280
+ structure=dict(variables=variables, mips=mips, priority_level=priority_level))
281
+
282
+ def count(self):
283
+ """
284
+ Count the number of variables linked to the VariablesGroup.
285
+ :return int: number of variables linked to the VariablesGroup
286
+ """
287
+ return len(self.get_variables())
288
+
289
+ def get_variables(self):
290
+ """
291
+ Return the list of Variables linked to the VariablesGroup.
292
+ :return list of Variable: list of Variable linked to VariablesGroup
293
+ """
294
+ return self.structure["variables"]
295
+
296
+ def get_mips(self):
297
+ """
298
+ Return the list of MIPs linked to the VariablesGroup.
299
+ :return list of DrObject: list of MIPs linked to VariablesGroup
300
+ """
301
+ return self.structure["mips"]
302
+
303
+ def get_priority_level(self):
304
+ """
305
+ Return the priority level of the VariablesGroup.
306
+ :return DrObject: priority level of VariablesGroup
307
+ """
308
+ return self.structure["priority_level"]
309
+
310
+ def print_content(self, level=0, add_content=True):
311
+ rep = super().print_content(level=level)
312
+ if add_content:
313
+ indent = " " * (level + 1)
314
+ rep.append(f"{indent}Variables included:")
315
+ for variable in self.get_variables():
316
+ rep.extend(variable.print_content(level=level + 2))
317
+ return rep
318
+
319
+ def filter_on_request(self, request_value):
320
+ request_type = request_value.DR_type
321
+ if request_type in ["variables", ]:
322
+ return True, request_value in self.get_variables()
323
+ elif request_type in ["mips", ]:
324
+ return True, request_value in self.get_mips()
325
+ elif request_type in ["priority_level", ]:
326
+ _, priority = is_link_id_or_value(self.get_priority_level().id)
327
+ _, req_priority = is_link_id_or_value(request_value.id)
328
+ return True, req_priority == priority
329
+ elif request_type in ["table_identifiers", "temporal_shape", "spatial_shape", "structure",
330
+ "physical_parameters", "modelling_realm", "esm-bcv", "cf_standard_names", "cell_methods",
331
+ "cell_measures"]:
332
+ return True, any(var.filter_on_request(request_value=request_value)[1] for var in self.get_variables())
333
+ else:
334
+ return super().filter_on_request(request_value=request_value)
335
+
336
+
337
+ class Opportunity(DRObjects):
338
+ def __init__(self, id, dr, DR_type="opportunities",
339
+ structure=dict(experiment_groups=list(), variable_groups=list(), data_request_themes=list(), time_subsets=list()),
340
+ **attributes):
341
+ super().__init__(id=id, dr=dr, DR_type=DR_type, structure=structure, **attributes)
342
+
343
+ def check(self):
344
+ super().check()
345
+ logger = get_logger()
346
+ if len(self.get_experiment_groups()) == 0:
347
+ logger.critical(f"No experiments group defined for {self.DR_type} id {self.id}")
348
+ if len(self.get_variable_groups()) == 0:
349
+ logger.critical(f"No variables group defined for {self.DR_type} id {self.id}")
350
+ if len(self.get_data_request_themes()) == 0:
351
+ logger.critical(f"No theme defined for {self.DR_type} id {self.id}")
352
+
353
+ @classmethod
354
+ def from_input(cls, dr, id, experiment_groups=list(), variable_groups=list(), data_request_themes=list(),
355
+ time_subsets=list(), mips=list(), **kwargs):
356
+
357
+ return super().from_input(DR_type="opportunities", dr=dr, id=id, elements=kwargs,
358
+ structure=dict(experiment_groups=experiment_groups, variable_groups=variable_groups,
359
+ data_request_themes=data_request_themes, time_subsets=time_subsets,
360
+ mips=mips))
361
+
362
+ def get_experiment_groups(self):
363
+ """
364
+ Return the list of ExperimentsGroup linked to the Opportunity.
365
+ :return list of ExperimentsGroup: list of ExperimentsGroup linked to Opportunity
366
+ """
367
+ return self.structure["experiment_groups"]
368
+
369
+ def get_variable_groups(self):
370
+ """
371
+ Return the list of VariablesGroup linked to the Opportunity.
372
+ :return list of VariablesGroup: list of VariablesGroup linked to Opportunity
373
+ """
374
+ return self.structure["variable_groups"]
375
+
376
+ def get_data_request_themes(self):
377
+ """
378
+ Return the list of themes linked to the Opportunity.
379
+ :return list of DRObject or ConstantValueObj: list of themes linked to Opportunity
380
+ """
381
+ return self.structure["data_request_themes"]
382
+
383
+ def get_themes(self):
384
+ """
385
+ Return the list of themes linked to the Opportunity.
386
+ :return list of DRObject or ConstantValueObj: list of themes linked to Opportunity
387
+ """
388
+ return self.get_data_request_themes()
389
+
390
+ def get_time_subsets(self):
391
+ """
392
+ Return the list of time subsets linked to the Opportunity.
393
+ :return list of DRObject: list of time subsets linked to Opportunity
394
+ """
395
+ return self.structure["time_subsets"]
396
+
397
+ def get_mips(self):
398
+ """
399
+ Return the list of MIPs linked to the Opportunity.
400
+ :return list of DRObject: list of MIPs linked to Opportunity
401
+ """
402
+ return self.structure["mips"]
403
+
404
+ def print_content(self, level=0, add_content=True):
405
+ rep = super().print_content(level=level)
406
+ if add_content:
407
+ indent = " " * (level + 1)
408
+ rep.append(f"{indent}Experiments groups included:")
409
+ for experiments_group in self.get_experiment_groups():
410
+ rep.extend(experiments_group.print_content(level=level + 2, add_content=False))
411
+ rep.append(f"{indent}Variables groups included:")
412
+ for variables_group in self.get_variable_groups():
413
+ rep.extend(variables_group.print_content(level=level + 2, add_content=False))
414
+ rep.append(f"{indent}Themes included:")
415
+ for theme in self.get_data_request_themes():
416
+ rep.extend(theme.print_content(level=level + 2, add_content=False))
417
+ rep.append(f"{indent}Time subsets included:")
418
+ for time_subset in self.get_time_subsets():
419
+ rep.extend(time_subset.print_content(level=level + 2, add_content=False))
420
+ return rep
421
+
422
+ def filter_on_request(self, request_value):
423
+ request_type = request_value.DR_type
424
+ if request_type in ["data_request_themes", ]:
425
+ return True, request_value in self.get_data_request_themes()
426
+ elif request_type in ["experiment_groups", ]:
427
+ return True, request_value in self.get_experiment_groups()
428
+ elif request_type in ["variable_groups", ]:
429
+ return True, request_value in self.get_variable_groups()
430
+ elif request_type in ["time_subset", ]:
431
+ return True, request_value in self.get_time_subsets()
432
+ elif request_type in ["mips", ]:
433
+ return True, request_value in self.get_mips() or \
434
+ any(var_grp.filter_on_request(request_value=request_value)[1]
435
+ for var_grp in self.get_variable_groups())
436
+ elif request_type in ["variables", "priority_level", "table_identifiers", "temporal_shape",
437
+ "spatial_shape", "structure", "physical_parameters", "modelling_realm", "esm-bcv",
438
+ "cf_standard_names", "cell_methods", "cell_measures"]:
439
+ return True, any(var_grp.filter_on_request(request_value=request_value)[1]
440
+ for var_grp in self.get_variable_groups())
441
+ elif request_type in ["experiments", ]:
442
+ return True, any(exp_grp.filter_on_request(request_value=request_value)[1]
443
+ for exp_grp in self.get_experiment_groups())
444
+ else:
445
+ return super().filter_on_request(request_value=request_value)
446
+
447
+
448
+ class DataRequest(object):
449
+ """
450
+ Data Request API object used to navigate among the Data Request and Vocabulary Server contents.
451
+ """
452
+ def __init__(self, input_database, VS, **kwargs):
453
+ """
454
+ Initialisation of the Data Request object
455
+ :param dict input_database: dictionary containing the DR database
456
+ :param VocabularyServer VS: reference Vocabulary Server to et information on objects
457
+ :param dict kwargs: additional parameters
458
+ """
459
+ self.VS = VS
460
+ self.content_version = input_database["version"]
461
+ self.structure = input_database
462
+ self.mapping = defaultdict(lambda: defaultdict(lambda: dict))
463
+ self.content = defaultdict(lambda: defaultdict(lambda: dict))
464
+ for op in input_database["opportunities"]:
465
+ self.content["opportunities"][op] = self.find_element("opportunities", op)
466
+
467
+ def check(self):
468
+ """
469
+ Method to check the content of the Data Request.
470
+ :return:
471
+ """
472
+ logger = get_logger()
473
+ logger.info("Check data request metadata")
474
+ logger.info("... Check experiments groups")
475
+ for elt in self.get_experiment_groups():
476
+ elt.check()
477
+ logger.info("... Check variables groups")
478
+ for elt in self.get_variable_groups():
479
+ elt.check()
480
+ logger.info("... Check opportunities")
481
+ for elt in self.get_opportunities():
482
+ elt.check()
483
+
484
+ @property
485
+ def software_version(self):
486
+ """
487
+ Method to get the version of the software.
488
+ :return str: version of the software
489
+ """
490
+ return version
491
+
492
+ @property
493
+ def version(self):
494
+ """
495
+ Method to get the version of both software and content
496
+ :return str : formatted version of the software and the content
497
+ """
498
+ return f"Software {self.software_version} - Content {self.content_version}"
499
+
500
+ @classmethod
501
+ def from_input(cls, json_input, version, **kwargs):
502
+ """
503
+ Method to instanciate the DataRequest object from a single input.
504
+ :param str or dict json_input: dictionary or name of the dedicated json file containing the export content
505
+ :param str version: version of the content
506
+ :param dict kwargs: additional parameters
507
+ :return DataRequest: instance of the DataRequest object.
508
+ """
509
+ DR_content, VS_content = cls._split_content_from_input_json(json_input, version=version)
510
+ VS = VocabularyServer(VS_content)
511
+ return cls(input_database=DR_content, VS=VS, **kwargs)
512
+
513
+ @classmethod
514
+ def from_separated_inputs(cls, DR_input, VS_input, **kwargs):
515
+ """
516
+ Method to instanciate the DataRequestObject from two inputs.
517
+ :param str or dict DR_input: dictionary or name of the json file containing the data request structure
518
+ :param str or dict VS_input: dictionary or name of the json file containing the vocabulary server
519
+ :param dict kwargs: additional parameters
520
+ :return DataRequest: instance of the DataRequest object
521
+ """
522
+ logger = get_logger()
523
+ if isinstance(DR_input, str) and os.path.isfile(DR_input):
524
+ DR = read_json_file(DR_input)
525
+ elif isinstance(DR_input, dict):
526
+ DR = copy.deepcopy(DR_input)
527
+ else:
528
+ logger.error("DR_input should be either the name of a json file or a dictionary.")
529
+ raise TypeError("DR_input should be either the name of a json file or a dictionary.")
530
+ if isinstance(VS_input, str) and os.path.isfile(VS_input):
531
+ VS = VocabularyServer.from_input(VS_input)
532
+ elif isinstance(VS_input, dict):
533
+ VS = VocabularyServer(copy.deepcopy(VS_input))
534
+ else:
535
+ logger.error("VS_input should be either the name of a json file or a dictionary.")
536
+ raise TypeError("VS_input should be either the name of a json file or a dictionary.")
537
+ return cls(input_database=DR, VS=VS, **kwargs)
538
+
539
+ @staticmethod
540
+ def _split_content_from_input_json(input_json, version):
541
+ """
542
+ Split the export if given through a single file and not from two files into the two dictionaries.
543
+ :param dict or str input_json: json input containing the bases or content as a dict
544
+ :param str version: version of the content used
545
+ :return dict, dict: two dictionaries containing the DR and the VS
546
+ """
547
+ logger = get_logger()
548
+ if not isinstance(version, str):
549
+ logger.error(f"Version should be a string, not {type(version).__name__}.")
550
+ raise TypeError(f"Version should be a string, not {type(version).__name__}.")
551
+ if isinstance(input_json, str) and os.path.isfile(input_json):
552
+ content = read_json_file(input_json)
553
+ elif isinstance(input_json, dict):
554
+ content = input_json
555
+ else:
556
+ logger.error("input_json should be either the name of a json file or a dictionary.")
557
+ raise TypeError("input_json should be either the name of a json file or a dictionary.")
558
+ DR, VS = transform_content(content, version=version)
559
+ return DR, VS
560
+
561
+ def __str__(self):
562
+ rep = list()
563
+ indent = " "
564
+ rep.append("Data Request content:")
565
+ rep.append(f"{indent}Experiments groups:")
566
+ for elt in self.get_experiment_groups():
567
+ rep.extend(elt.print_content(level=2))
568
+ rep.append(f"{indent}Variables groups:")
569
+ for elt in self.get_variable_groups():
570
+ rep.extend(elt.print_content(level=2))
571
+ rep.append(f"{indent}Opportunities:")
572
+ for elt in self.get_opportunities():
573
+ rep.extend(elt.print_content(level=2))
574
+ return os.linesep.join(rep)
575
+
576
+ def get_experiment_groups(self):
577
+ """
578
+ Get the ExperimentsGroup of the Data Request.
579
+ :return list of ExperimentsGroup: list of the ExperimentsGroup of the DR content.
580
+ """
581
+ return [self.content["experiment_groups"][key] for key in sorted(list(self.content["experiment_groups"]))]
582
+
583
+ def get_experiment_group(self, id):
584
+ """
585
+ Get the ExperimentsGroup associated with a specific id.
586
+ :param str id: id of the ExperimentsGroup
587
+ :return ExperimentsGroup: the ExperimentsGroup associated with the input id
588
+ """
589
+ rep = self.find_element("experiment_groups", id, default=None)
590
+ if rep is not None:
591
+ return rep
592
+ else:
593
+ raise ValueError(f"Could not find experiments group {id} among {self.get_experiment_groups()}.")
594
+
595
+ def get_variable_groups(self):
596
+ """
597
+ Get the VariablesGroup of the Data Request.
598
+ :return list of VariablesGroup: list of the VariablesGroup of the DR content.
599
+ """
600
+ return [self.content["variable_groups"][key] for key in sorted(list(self.content["variable_groups"]))]
601
+
602
+ def get_variable_group(self, id):
603
+ """
604
+ Get the VariablesGroup associated with a specific id.
605
+ :param str id: id of the VariablesGroup
606
+ :return VariablesGroup: the VariablesGroup associated with the input id
607
+ """
608
+ rep = self.find_element("variable_groups", id, default=None)
609
+ if rep is not None:
610
+ return rep
611
+ else:
612
+ raise ValueError(f"Could not find variables group {id}.")
613
+
614
+ def get_opportunities(self):
615
+ """
616
+ Get the Opportunity of the Data Request.
617
+ :return list of Opportunity: list of the Opportunity of the DR content.
618
+ """
619
+ return [self.content["opportunities"][key] for key in sorted(list(self.content["opportunities"]))]
620
+
621
+ def get_opportunity(self, id):
622
+ """
623
+ Get the Opportunity associated with a specific id.
624
+ :param str id: id of the Opportunity
625
+ :return Opportunity: the Opportunity associated with the input id
626
+ """
627
+ rep = self.find_element("opportunities", id, default=None)
628
+ if rep is not None:
629
+ return rep
630
+ else:
631
+ raise ValueError(f"Could not find opportunity {id}.")
632
+
633
+ def get_variables(self):
634
+ """
635
+ Get the Variable of the Data Request.
636
+ :return list of Variable: list of the Variable of the DR content.
637
+ """
638
+ rep = set()
639
+ for var_grp in self.get_variable_groups():
640
+ rep = rep | set(var_grp.get_variables())
641
+ rep = sorted(list(rep))
642
+ return rep
643
+
644
+ def get_mips(self):
645
+ """
646
+ Get the MIPs of the Data Request.
647
+ :return list of DRObject or ConstantValueObj: list of the MIPs of the DR content.
648
+ """
649
+ rep = set()
650
+ for op in self.get_opportunities():
651
+ rep = rep | set(op.get_mips())
652
+ for var_grp in self.get_variable_groups():
653
+ rep = rep | set(var_grp.get_mips())
654
+ rep = sorted(list(rep))
655
+ return rep
656
+
657
+ def get_experiments(self):
658
+ """
659
+ Get the experiments of the Data Request.
660
+ :return list of DRObject: list of the experiments of the DR content.
661
+ """
662
+ rep = set()
663
+ for exp_grp in self.get_experiment_groups():
664
+ rep = rep | set(exp_grp.get_experiments())
665
+ rep = sorted(list(rep))
666
+ return rep
667
+
668
+ def get_data_request_themes(self):
669
+ """
670
+ Get the themes of the Data Request.
671
+ :return list of DRObject: list of the themes of the DR content.
672
+ """
673
+ rep = set()
674
+ for op in self.get_opportunities():
675
+ rep = rep | set(op.get_themes())
676
+ rep = sorted(list(rep))
677
+ return rep
678
+
679
+ def find_variables_per_priority(self, priority):
680
+ """
681
+ Find all the variables which have a specified priority.
682
+ :param DRObjects or ConstantValueObj or str priority: priority to be considered
683
+ :return list of Variable: list of the variables which have a specified priority.
684
+ """
685
+ return self.filter_elements_per_request(element_type="variables", requests=dict(priority_level=[priority, ]))
686
+
687
+ def find_opportunities_per_theme(self, theme):
688
+ """
689
+ Find all the opportunities which are linked to a specified theme.
690
+ :param DRObjects or ConstantValueObj or str theme: theme to be considered
691
+ :return list of Opportunity: list of the opportunities which are linked to a specified theme.
692
+ """
693
+ return self.filter_elements_per_request(element_type="opportunities", requests=dict(data_request_themes=[theme, ]))
694
+
695
+ def find_experiments_per_theme(self, theme):
696
+ """
697
+ Find all the experiments which are linked to a specified theme.
698
+ :param DRObjects or ConstantValueObj or str theme: theme to be considered
699
+ :return list of DRObjects or ConstantValueObj: list of the experiments which are linked to a specified theme.
700
+ """
701
+ return self.filter_elements_per_request(element_type="experiments", requests=dict(data_request_themes=[theme, ]))
702
+
703
+ def find_variables_per_theme(self, theme):
704
+ """
705
+ Find all the variables which are linked to a specified theme.
706
+ :param DRObjects or ConstantValueObj or str theme: theme to be considered
707
+ :return list of Variable: list of the variables which are linked to a specified theme.
708
+ """
709
+ return self.filter_elements_per_request(element_type="variables", requests=dict(data_request_themes=[theme, ]))
710
+
711
+ def find_mips_per_theme(self, theme):
712
+ """
713
+ Find all the MIPs which are linked to a specified theme.
714
+ :param DRObjects or ConstantValueObj or str theme: theme to be considered
715
+ :return list of DRObjects or ConstantValueObj: list of the MIPs which are linked to a specified theme.
716
+ """
717
+ return self.filter_elements_per_request(element_type="mips", requests=dict(data_request_themes=[theme, ]))
718
+
719
+ def find_themes_per_opportunity(self, opportunity):
720
+ """
721
+ Find all the themes which are linked to a specified opportunity.
722
+ :param Opportunity or str opportunity: opportunity to be considered
723
+ :return list of DRObjects or ConstantValueObj: list of the themes which are linked to a specified opportunity.
724
+ """
725
+ return self.filter_elements_per_request(element_type="data_request_themes", requests=dict(opportunities=[opportunity, ]))
726
+
727
+ def find_experiments_per_opportunity(self, opportunity):
728
+ """
729
+ Find all the experiments which are linked to a specified opportunity.
730
+ :param Opportunity or str opportunity: opportunity to be considered
731
+ :return list of DRObjects or ConstantValueObj: list of the experiments which are linked to a specified opportunity.
732
+ """
733
+ return self.filter_elements_per_request(element_type="experiments", requests=dict(opportunities=[opportunity, ]))
734
+
735
+ def find_variables_per_opportunity(self, opportunity):
736
+ """
737
+ Find all the variables which are linked to a specified opportunity.
738
+ :param Opportunity or str opportunity: opportunity to be considered
739
+ :return list of Variable: list of the variables which are linked to a specified opportunity.
740
+ """
741
+ return self.filter_elements_per_request(element_type="variables", requests=dict(opportunities=[opportunity, ]))
742
+
743
+ def find_mips_per_opportunity(self, opportunity):
744
+ """
745
+ Find all the MIPs which are linked to a specified opportunity.
746
+ :param Opportunity or str opportunity: opportunity to be considered
747
+ :return list of DRObjects or ConstantValueObj: list of the MIPs which are linked to a specified opportunity.
748
+ """
749
+ return self.filter_elements_per_request(element_type="mips", requests=dict(opportunities=[opportunity, ]))
750
+
751
+ def find_opportunities_per_variable(self, variable):
752
+ """
753
+ Find all the opportunities which are linked to a specified variable.
754
+ :param Variable or str variable: variable to be considered
755
+ :return list of Opportunity: list of the opportunities which are linked to a specified variable.
756
+ """
757
+ return self.filter_elements_per_request(element_type="opportunities", requests=dict(variables=[variable, ]))
758
+
759
+ def find_themes_per_variable(self, variable):
760
+ """
761
+ Find all the themes which are linked to a specified variable.
762
+ :param Variable or str variable: variable to be considered
763
+ :return list of DRObjects or ConstantValueObj: list of the themes which are linked to a specified variable.
764
+ """
765
+ return self.filter_elements_per_request(element_type="data_request_themes", requests=dict(variables=[variable, ]))
766
+
767
+ def find_mips_per_variable(self, variable):
768
+ """
769
+ Find all the MIPs which are linked to a specified variable.
770
+ :param Variable or str variable: variable to be considered
771
+ :return list of DRObjects or ConstantValueObj: list of the MIPs which are linked to a specified variable.
772
+ """
773
+ return self.filter_elements_per_request(element_type="mips", requests=dict(variables=[variable, ]))
774
+
775
+ def find_opportunities_per_experiment(self, experiment):
776
+ """
777
+ Find all the opportunities which are linked to a specified experiment.
778
+ :param DRObjects or ConstantValueObj or str experiment: experiment to be considered
779
+ :return list of Opportunity: list of the opportunities which are linked to a specified experiment.
780
+ """
781
+ return self.filter_elements_per_request(element_type="opportunities", requests=dict(experiments=[experiment, ]))
782
+
783
+ def find_themes_per_experiment(self, experiment):
784
+ """
785
+ Find all the themes which are linked to a specified experiment.
786
+ :param DRObjects or ConstantValueObj or str experiment: experiment to be considered
787
+ :return list of DRObjects or ConstantValueObj: list of the themes which are linked to a specified experiment.
788
+ """
789
+ return self.filter_elements_per_request(element_type="data_request_themes", requests=dict(experiments=[experiment, ]))
790
+
791
+ def find_element_per_identifier_from_vs(self, element_type, key, value, default=False, **kwargs):
792
+ """
793
+ Find an element of a specific type and specified by a value (of a given kind) from vocabulary server.
794
+ :param str element_type: type of the element to be found (same as in vocabulary server).
795
+ :param str key: type of the value key to be looked for ("id", "name"...)
796
+ :param str value: value to be looked for
797
+ :param default: default value to be used if the value is not found
798
+ :param dict kwargs: additional attributes to be used for vocabulary server search.
799
+ :return Opportunity or VariablesGroup or ExperimentsGroup or Variables or DRObjects or ConstantValueObj or default: the element found from vocabulary server or the default value if none is found.
800
+ """
801
+ if key in ["id", ]:
802
+ value = build_link_from_id(value)
803
+ rep = self.VS.get_element(element_type=element_type, element_id=value, id_type=key, default=default, **kwargs)
804
+ if rep not in [default, ]:
805
+ if element_type in ["opportunities", ]:
806
+ rep = Opportunity.from_input(dr=self, **rep,
807
+ **self.structure.get("opportunities", dict()).get(rep["id"], dict()))
808
+ elif element_type in ["variable_groups", ]:
809
+ rep = VariablesGroup.from_input(dr=self, **rep,
810
+ **self.structure.get("variable_groups", dict()).get(rep["id"], dict()))
811
+ elif element_type in ["experiment_groups", ]:
812
+ rep = ExperimentsGroup.from_input(dr=self, **rep,
813
+ **self.structure.get("experiment_groups", dict()).get(rep["id"], dict()))
814
+ elif element_type in ["variables", ]:
815
+ rep = Variable.from_input(dr=self, **rep)
816
+ else:
817
+ rep = DRObjects.from_input(dr=self, id=rep["id"], DR_type=element_type, elements=rep)
818
+ return rep
819
+
820
+ def find_element_from_vs(self, element_type, value, default=False):
821
+ """
822
+ Find an element of a specific type and specified by a value from vocabulary server.
823
+ Update the content and mapping list not to have to ask the vocabulary server again for it.
824
+ :param str element_type: kind of element to be looked for
825
+ :param str value: value to be looked for
826
+ :param default: default value to be returned if no value found
827
+ :return: element corresponding to the specified value of a given type if found, else the default value
828
+ """
829
+ rep = self.find_element_per_identifier_from_vs(element_type=element_type, value=value, key="id", default=None)
830
+ if rep is not None:
831
+ self.content[element_type][rep.id] = rep
832
+ else:
833
+ rep = self.find_element_per_identifier_from_vs(element_type=element_type, value=value, key="name",
834
+ default=default)
835
+ if rep not in [default, ]:
836
+ self.content[element_type][rep.id] = rep
837
+ self.mapping[element_type][rep.name] = rep
838
+ return rep
839
+
840
+ def find_element(self, element_type, value, default=False):
841
+ """
842
+ Find an element of a specific type and specified by a value from mapping/content if existing,
843
+ else from vocabulary server.
844
+ :param str element_type: kind of element to be found
845
+ :param str value: value to be looked for
846
+ :param default: value to be returned if non found
847
+ :return: the found element if existing, else the default value
848
+ """
849
+ if value in self.content[element_type]:
850
+ return self.content[element_type][value]
851
+ elif value in self.mapping[element_type]:
852
+ return self.mapping[element_type][value]
853
+ else:
854
+ return self.find_element_from_vs(element_type=element_type, value=value, default=default)
855
+
856
+ def get_elements_per_kind(self, element_type):
857
+ """
858
+ Return the list of elements of kind element_type
859
+ :param str element_type: the kind of the elements to be found
860
+ :return list: the list of elements of kind element_type
861
+ """
862
+ logger = get_logger()
863
+ if element_type in ["opportunities", ]:
864
+ elements = self.get_opportunities()
865
+ elif element_type in ["experiment_groups", ]:
866
+ elements = self.get_experiment_groups()
867
+ elif element_type in ["variable_groups", ]:
868
+ elements = self.get_variable_groups()
869
+ elif element_type in ["variables", ]:
870
+ elements = self.get_variables()
871
+ elif element_type in ["experiments", ]:
872
+ elements = self.get_experiments()
873
+ elif element_type in ["data_request_themes", ]:
874
+ elements = self.get_data_request_themes()
875
+ elif element_type in ["mips", ]:
876
+ elements = self.get_mips()
877
+ else:
878
+ logger.debug("Find elements list from vocabulary server.")
879
+ element_type, elements_ids = self.VS.get_element_type_ids(element_type)
880
+ elements = [self.find_element(element_type, id) for id in elements_ids]
881
+ return elements
882
+
883
+ @staticmethod
884
+ def _two_elements_filtering(filtering_elt_1, filtering_elt_2, list_to_filter):
885
+ """
886
+ Check if a list of elements can be filtered by two values
887
+ :param filtering_elt_1: first element for filtering
888
+ :param filtering_elt_2: second element for filtering
889
+ :param list list_to_filter: list of elements to be filtered
890
+ :return bool, bool: a boolean to tell if it relevant to filter list_to_filter by filtering_elt_1 and filtering_elt_2,
891
+ a boolean to tell, if relevant, if filtering_elt_1 and filtering_elt_2 are linked to list_to_filter
892
+ """
893
+ elt = list_to_filter[0]
894
+ filtered_found_1, found_1 = elt.filter_on_request(filtering_elt_1)
895
+ filtered_found_2, found_2 = elt.filter_on_request(filtering_elt_2)
896
+ filtered_found = filtered_found_1 and filtered_found_2
897
+ found = found_1 and found_2
898
+ if filtered_found and not found:
899
+ found = any([elt.filter_on_request(filtering_elt_1)[1] and
900
+ elt.filter_on_request(filtering_elt_2)[1]
901
+ for elt in list_to_filter])
902
+ return filtered_found, found
903
+
904
+ def filter_elements_per_request(self, element_type, requests=dict(), operation="all", skip_if_missing=False):
905
+ """
906
+ Filter the elements of kind element_type with a dictionary of requests.
907
+ :param str element_type: kind of elements to be filtered
908
+ :param dict requests: dictionary of the filters to be applied
909
+ :param str operation: should at least one filter be applied ("any") or all filters be fulfilled ("all")
910
+ :param bool skip_if_missing: if a request filter is not found, should it be skipped or should an error be raised?
911
+ :return: list of elements of kind element_type which correspond to the filtering requests
912
+ """
913
+ logger = get_logger()
914
+ if operation not in ["any", "all"]:
915
+ raise ValueError(f"Operation does not accept {operation} as value: choose among 'any' (match at least one requirement) and 'all' (match all requirements)")
916
+ else:
917
+ # Prepare the request dictionary
918
+ request_dict = defaultdict(list)
919
+ for (req, values) in requests.items():
920
+ if not isinstance(values, list):
921
+ values = [values, ]
922
+ for val in values:
923
+ if isinstance(val, str):
924
+ new_val = self.find_element(element_type=req, value=val, default=None)
925
+ else:
926
+ new_val = val
927
+ if new_val is not None:
928
+ request_dict[req].append(new_val)
929
+ elif skip_if_missing:
930
+ logger.warning(f"Could not find value {val} for element type {req}, skip it.")
931
+ else:
932
+ logger.error(f"Could not find value {val} for element type {req}.")
933
+ raise ValueError(f"Could not find value {val} for element type {req}.")
934
+ # Get elements corresponding to element_type
935
+ elements = self.get_elements_per_kind(element_type)
936
+ # Filter elements
937
+ rep = defaultdict(lambda: defaultdict(set))
938
+ for (request, values) in request_dict.items():
939
+ for val in values:
940
+ for elt in elements:
941
+ filtered_found, found = elt.filter_on_request(val)
942
+ if not filtered_found:
943
+ filtered_found, found = val.filter_on_request(elt)
944
+ if not filtered_found:
945
+ filtered_found, found = self._two_elements_filtering(val, elt, self.get_experiment_groups())
946
+ if not filtered_found:
947
+ filtered_found, found = self._two_elements_filtering(val, elt, self.get_variables())
948
+ if not filtered_found:
949
+ filtered_found, found = self._two_elements_filtering(val, elt, self.get_variable_groups())
950
+ if not filtered_found:
951
+ filtered_found, found = self._two_elements_filtering(val, elt, self.get_opportunities())
952
+ if not filtered_found:
953
+ logger.error(f"Could not filter {element_type} by {request}")
954
+ raise ValueError(f"Could not filter {element_type} by {request}")
955
+ if found:
956
+ rep[request][val.id].add(elt)
957
+ if len(rep) == 0:
958
+ rep_list = set(elements)
959
+ elif operation in ["any", ]:
960
+ rep_list = set()
961
+ for req in rep:
962
+ for val in rep[req]:
963
+ rep_list = rep_list | rep[req][val]
964
+ elif operation in ["all", ]:
965
+ rep_list = set(elements)
966
+ for req in rep:
967
+ for val in rep[req]:
968
+ rep_list = rep_list & rep[req][val]
969
+ else:
970
+ raise ValueError(f"Unknown value {operation} for operation (only 'all' and 'any' are available).")
971
+ rep_list = sorted(list(rep_list))
972
+ return rep_list
973
+
974
+ def find_opportunities(self, operation="any", skip_if_missing=False, **kwargs):
975
+ """
976
+ Find the opportunities corresponding to filtering criteria.
977
+ :param str operation: should at least one filter be applied ("any") or all filters be fulfilled ("all")
978
+ :param bool skip_if_missing: if a request filter is not found, should it be skipped or should an error be raised?
979
+ :param dict kwargs: filters to be applied
980
+ :return list of Opportunity: opportunities linked to the filters
981
+ """
982
+ return self.filter_elements_per_request(element_type="opportunities", operation=operation,
983
+ skip_if_missing=skip_if_missing, requests=kwargs)
984
+
985
+ def find_experiments(self, operation="any", skip_if_missing=False, **kwargs):
986
+ """
987
+ Find the experiments corresponding to filtering criteria.
988
+ :param str operation: should at least one filter be applied ("any") or all filters be fulfilled ("all")
989
+ :param bool skip_if_missing: if a request filter is not found, should it be skipped or should an error be raised?
990
+ :param dict kwargs: filters to be applied
991
+ :return list of DRObjects: experiments linked to the filters
992
+ """
993
+ return self.filter_elements_per_request(element_type="experiments", operation=operation,
994
+ skip_if_missing=skip_if_missing, requests=kwargs)
995
+
996
+ def find_variables(self, operation="any", skip_if_missing=False, **kwargs):
997
+ """
998
+ Find the variables corresponding to filtering criteria.
999
+ :param str operation: should at least one filter be applied ("any") or all filters be fulfilled ("all")
1000
+ :param bool skip_if_missing: if a request filter is not found, should it be skipped or should an error be raised?
1001
+ :param dict kwargs: filters to be applied
1002
+ :return list of Variable: variables linked to the filters
1003
+ """
1004
+ return self.filter_elements_per_request(element_type="variables", operation=operation,
1005
+ skip_if_missing=skip_if_missing, requests=kwargs)
1006
+
1007
+ def sort_func(self, data_list, sorting_request=list()):
1008
+ """
1009
+ Method to sort a list of objects based on some criteria
1010
+ :param list data_list: the list of objects to be sorted
1011
+ :param list sorting_request: list of criteria to sort the input list
1012
+ :return list: sorted list
1013
+ """
1014
+ sorting_request = copy.deepcopy(sorting_request)
1015
+ if len(sorting_request) == 0:
1016
+ return sorted(data_list, key=lambda x: x.id)
1017
+ else:
1018
+ sorting_val = sorting_request.pop(0)
1019
+ sorting_values_dict = defaultdict(list)
1020
+ for data in data_list:
1021
+ sorting_values_dict[data.get(sorting_val)].append(data)
1022
+ rep = list()
1023
+ for elt in sorted(list(sorting_values_dict)):
1024
+ rep.extend(self.sort_func(sorting_values_dict[elt], sorting_request))
1025
+ return rep
1026
+
1027
+ def export_data(self, main_data, output_file, filtering_requests=dict(), filtering_operation="all",
1028
+ filtering_skip_if_missing=False, export_columns_request=list(), sorting_request=list(), **kwargs):
1029
+ """
1030
+ Method to export a filtered and sorted list of data to a csv file.
1031
+ :param str main_data: kind of data to be exported
1032
+ :param str output_file: name of the output faile (csv)
1033
+ :param dict filtering_requests: filtering request to be applied to the list of object of main_data kind
1034
+ :param str filtering_operation: filtering operation to be applied to the list of object of main_data kind
1035
+ :param bool filtering_skip_if_missing: filtering skip_if_missing to be applied to the list of object of main_data kind
1036
+ :param list export_columns_request: columns to be putted in the output file
1037
+ :param list sorting_request: sorting criteria to be applied
1038
+ :param dict kwargs: additional arguments to be given to function write_csv_output_file_content
1039
+ :return: an output csv file
1040
+ """
1041
+ filtered_data = self.filter_elements_per_request(element_type=main_data, requests=filtering_requests,
1042
+ operation=filtering_operation,
1043
+ skip_if_missing=filtering_skip_if_missing)
1044
+ sorted_filtered_data = self.sort_func(filtered_data, sorting_request)
1045
+
1046
+ export_columns_request.insert(0, "id")
1047
+ content = list()
1048
+ content.append(export_columns_request)
1049
+ for data in sorted_filtered_data:
1050
+ content.append([str(data.__getattr__(key)) for key in export_columns_request])
1051
+
1052
+ write_csv_output_file_content(output_file, content, **kwargs)
1053
+
1054
+ def export_summary(self, lines_data, columns_data, output_file, sorting_line="id", title_line="name",
1055
+ sorting_column="id", title_column="name", filtering_requests=dict(), filtering_operation="all",
1056
+ filtering_skip_if_missing=False, **kwargs):
1057
+ """
1058
+ Create a 2D tables of csv kind which give the linked between the two list of elements kinds specified
1059
+ :param str lines_data: kind of data to be put in row
1060
+ :param str columns_data: kind of data to be put in range
1061
+ :param str output_file: name of the output file (csv)
1062
+ :param str sorting_line: criteria to sort raw data
1063
+ :param str title_line: attribute to be used for raw header
1064
+ :param str sorting_column: criteria to sort range data
1065
+ :param str title_column: attribute to be used for range header
1066
+ :param dict filtering_requests: filtering request to be applied to the list of object of main_data kind
1067
+ :param str filtering_operation: filtering operation to be applied to the list of object of main_data kind
1068
+ :param bool filtering_skip_if_missing: filtering skip_if_missing to be applied to the list of object of main_data kind
1069
+ :param dict kwargs: additional arguments to be given to function write_csv_output_file_content
1070
+ :return: a csv output file
1071
+ """
1072
+ logger = get_logger()
1073
+ logger.debug(f"Generate summary for {lines_data}/{columns_data}")
1074
+ filtered_data = self.filter_elements_per_request(element_type=lines_data, requests=filtering_requests,
1075
+ operation=filtering_operation,
1076
+ skip_if_missing=filtering_skip_if_missing)
1077
+ sorted_filtered_data = self.sort_func(filtered_data, sorting_request=[sorting_line, ])
1078
+ columns_datasets = self.filter_elements_per_request(element_type=columns_data)
1079
+ columns_datasets = self.sort_func(columns_datasets, sorting_request=[sorting_column, ])
1080
+ columns_title = [str(elt.__getattr__(title_column)) for elt in columns_datasets]
1081
+ table_title = f"{lines_data} {title_line} / {columns_data} {title_column}"
1082
+
1083
+ nb_lines = len(sorted_filtered_data)
1084
+ logger.debug(f"{nb_lines} elements found for {lines_data}")
1085
+ logger.debug(f"{len(columns_title)} found elements for {columns_data}")
1086
+
1087
+ logger.debug("Generate summary")
1088
+ content = defaultdict(list)
1089
+ for (i, data) in enumerate(columns_datasets):
1090
+ logger.debug(f"Deal with column {i}/{len(columns_title)}")
1091
+ filter_line_datasets = self.filter_elements_per_request(element_type=lines_data,
1092
+ requests={data.DR_type: data},
1093
+ operation="all")
1094
+ for line_data in filtered_data:
1095
+ line_data_title = line_data.__getattr__(title_line)
1096
+ if line_data in filter_line_datasets:
1097
+ content[line_data_title].append("x")
1098
+ else:
1099
+ content[line_data_title].append("")
1100
+
1101
+ logger.debug("Format summary")
1102
+ rep = list()
1103
+ rep.append([table_title, ] + columns_title)
1104
+ for line_data in filtered_data:
1105
+ line_data_title = line_data.__getattr__(title_line)
1106
+ rep.append([line_data_title, ] + content[line_data_title])
1107
+
1108
+ logger.debug("Write summary")
1109
+ write_csv_output_file_content(output_file, rep, **kwargs)
1110
+
1111
+
1112
+ if __name__ == "__main__":
1113
+ change_log_file(default=True)
1114
+ change_log_level("debug")
1115
+ parser = argparse.ArgumentParser()
1116
+ parser.add_argument("--DR_json", default="DR_request_basic_dump2.json")
1117
+ parser.add_argument("--VS_json", default="VS_request_basic_dump2.json")
1118
+ args = parser.parse_args()
1119
+ DR = DataRequest.from_separated_inputs(args.DR_json, args.VS_json)
1120
+ print(DR)