imas-python 2.0.1__py3-none-any.whl → 2.1.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.
imas/_version.py CHANGED
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '2.0.1'
21
- __version_tuple__ = version_tuple = (2, 0, 1)
31
+ __version__ = version = '2.1.0'
32
+ __version_tuple__ = version_tuple = (2, 1, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -71,10 +71,7 @@ class ALContext:
71
71
  Returns:
72
72
  The created context.
73
73
  """
74
- args = [self.ctx, path, rwmode]
75
- if datapath: # AL4 compatibility: datapath arg was added in AL5
76
- args.append(datapath)
77
- status, ctx = ll_interface.begin_global_action(*args)
74
+ status, ctx = ll_interface.begin_global_action(self.ctx, path, rwmode, datapath)
78
75
  if status != 0:
79
76
  raise LowlevelError("global_action", status)
80
77
  return ALContext(ctx)
@@ -5,30 +5,29 @@ import getpass
5
5
  import logging
6
6
  import os
7
7
  from collections import deque
8
+ import re
8
9
  from typing import Any, Deque, List, Optional, Union
9
10
  from urllib.parse import urlparse
10
11
 
12
+ from packaging.version import Version
13
+
14
+ import imas
11
15
  from imas.backends.db_entry_impl import GetSampleParameters, GetSliceParameters
12
16
  from imas.db_entry import DBEntryImpl
13
17
  from imas.exception import DataEntryException, LowlevelError
14
18
  from imas.ids_convert import NBCPathMap, dd_version_map_from_factories
15
19
  from imas.ids_defs import (
16
- ASCII_BACKEND,
17
20
  CHAR_DATA,
18
21
  CLOSE_PULSE,
19
22
  CREATE_PULSE,
20
23
  ERASE_PULSE,
21
24
  FORCE_CREATE_PULSE,
22
25
  FORCE_OPEN_PULSE,
23
- HDF5_BACKEND,
24
26
  IDS_TIME_MODE_UNKNOWN,
25
27
  IDS_TIME_MODES,
26
28
  INTEGER_DATA,
27
- MDSPLUS_BACKEND,
28
- MEMORY_BACKEND,
29
29
  OPEN_PULSE,
30
30
  READ_OP,
31
- UDA_BACKEND,
32
31
  UNDEFINED_INTERP,
33
32
  UNDEFINED_TIME,
34
33
  WRITE_OP,
@@ -40,16 +39,9 @@ from imas.ids_toplevel import IDSToplevel
40
39
  from .al_context import ALContext, LazyALContext
41
40
  from .db_entry_helpers import delete_children, get_children, put_children
42
41
  from .imas_interface import LLInterfaceError, has_imas, ll_interface
43
- from .mdsplus_model import ensure_data_dir, mdsplus_model_dir
42
+ from .mdsplus_model import mdsplus_model_dir
44
43
  from .uda_support import extract_idsdef, get_dd_version_from_idsdef_xml
45
44
 
46
- _BACKEND_NAME = {
47
- ASCII_BACKEND: "ascii",
48
- HDF5_BACKEND: "hdf5",
49
- MEMORY_BACKEND: "memory",
50
- MDSPLUS_BACKEND: "mdsplus",
51
- UDA_BACKEND: "uda",
52
- }
53
45
  _OPEN_MODES = {
54
46
  "r": OPEN_PULSE,
55
47
  "a": FORCE_OPEN_PULSE,
@@ -71,13 +63,26 @@ def require_imas_available():
71
63
  class ALDBEntryImpl(DBEntryImpl):
72
64
  """DBEntry implementation using imas_core as a backend."""
73
65
 
74
- """Map to the expected open_pulse (AL4) / begin_dataentry_action (AL5) argument."""
66
+ def __init__(self, uri: str, mode: int, factory: IDSFactory):
67
+ # Setup backend and lowlevel Access Layer:
68
+ backend = urlparse(uri).path.lower().lstrip("/")
69
+ self._setup_backend(backend, mode, factory)
70
+ status, ctx = ll_interface.begin_dataentry_action(uri, mode)
71
+ if status != 0:
72
+ raise LowlevelError("opening/creating data entry", status)
75
73
 
76
- def __init__(self, backend: str, ctx: ALContext, factory: IDSFactory):
77
74
  self.backend = backend
78
- self._db_ctx = ctx
75
+ self._db_ctx = ALContext(ctx)
79
76
  self._ids_factory = factory
80
77
  self._lazy_ctx_cache: Deque[ALContext] = deque()
78
+ self._uri = uri
79
+
80
+ # Parse query options, mimic logic in AL-Core instead of using
81
+ # urllib.parse.parse_qs(..). See https://github.com/jholloc/simple-uri-parser
82
+ self._querydict = {}
83
+ for option in re.split("[&;?]", urlparse(self._uri).query):
84
+ name, _, value = option.partition("=")
85
+ self._querydict[name] = value
81
86
 
82
87
  @classmethod
83
88
  def from_uri(cls, uri: str, mode: str, factory: IDSFactory) -> "ALDBEntryImpl":
@@ -85,7 +90,7 @@ class ALDBEntryImpl(DBEntryImpl):
85
90
  if mode not in _OPEN_MODES:
86
91
  modes = list(_OPEN_MODES)
87
92
  raise ValueError(f"Unknown mode {mode!r}, was expecting any of {modes}")
88
- return cls._from_uri(uri, _OPEN_MODES[mode], factory)
93
+ return cls(uri, _OPEN_MODES[mode], factory)
89
94
 
90
95
  @classmethod
91
96
  def from_pulse_run(
@@ -108,60 +113,18 @@ class ALDBEntryImpl(DBEntryImpl):
108
113
  data_version = data_version or factory.dd_version
109
114
  options = options if options else ""
110
115
 
111
- if ll_interface._al_version.major >= 5:
112
- # We need a URI for AL 5 or later, construct from legacy parameters
113
- status, uri = ll_interface.build_uri_from_legacy_parameters(
114
- backend_id, pulse, run, user_name, db_name, data_version, options
115
- )
116
- if status != 0:
117
- raise LowlevelError("build URI from legacy parameters", status)
118
-
119
- return cls._from_uri(uri, mode, factory)
120
-
121
- else:
122
- # AL4 legacy support:
123
- backend = _BACKEND_NAME.get(backend_id, "")
124
- cls._setup_backend(backend, mode, factory, user_name, db_name, run)
125
-
126
- status, ctx = ll_interface.begin_pulse_action(
127
- backend_id, pulse, run, user_name, db_name, data_version
128
- )
129
- if status != 0:
130
- raise LowlevelError("begin pulse action", status)
131
-
132
- status = ll_interface.open_pulse(ctx, mode, options)
133
- if status != 0:
134
- raise LowlevelError("opening/creating data entry", status)
135
-
136
- return cls(backend, ALContext(ctx), factory)
137
-
138
- @classmethod
139
- def _from_uri(cls, uri: str, mode: int, factory: IDSFactory) -> "ALDBEntryImpl":
140
- """Helper method to actually open/create the dataentry."""
141
- backend = urlparse(uri).path.lower().lstrip("/")
142
- cls._setup_backend(backend, mode, factory)
143
-
144
- status, ctx = ll_interface.begin_dataentry_action(uri, mode)
116
+ # Construct URI from legacy parameters
117
+ status, uri = ll_interface.build_uri_from_legacy_parameters(
118
+ backend_id, pulse, run, user_name, db_name, data_version, options
119
+ )
145
120
  if status != 0:
146
- raise LowlevelError("opening/creating data entry", status)
121
+ raise LowlevelError("build URI from legacy parameters", status)
147
122
 
148
- return cls(backend, ALContext(ctx), factory)
123
+ return cls(uri, mode, factory)
149
124
 
150
125
  @classmethod
151
- def _setup_backend(
152
- cls,
153
- backend: str,
154
- mode: int,
155
- factory: IDSFactory,
156
- user_name: str = "",
157
- db_name="",
158
- run=1,
159
- ) -> None:
160
- """Custom logic for preparing some backends.
161
-
162
- Note: user_name, db_name and run are only used for AL 4.x, they can be
163
- omitted when using AL 5 or later.
164
- """
126
+ def _setup_backend(cls, backend: str, mode: int, factory: IDSFactory) -> None:
127
+ """Custom logic for preparing some backends."""
165
128
  if backend == "mdsplus":
166
129
  # MDSplus models:
167
130
  if mode != OPEN_PULSE:
@@ -170,22 +133,6 @@ class ALDBEntryImpl(DBEntryImpl):
170
133
  if ids_path:
171
134
  os.environ["ids_path"] = ids_path
172
135
 
173
- if ll_interface._al_version.major == 4:
174
- # Ensure the data directory exists
175
- # Note: MDSPLUS model directory only uses the major version component of
176
- # IMAS_VERSION, so we'll take the first character of IMAS_VERSION:
177
- version = factory.version[0]
178
- ensure_data_dir(user_name, db_name, version, run)
179
-
180
- elif backend == "hdf5":
181
- pass # nothing to set up
182
-
183
- elif backend == "memory":
184
- pass # nothing to set up
185
-
186
- elif backend == "ascii":
187
- pass # nothing to set up
188
-
189
136
  elif backend == "uda":
190
137
  # Set IDSDEF_PATH to point the UDA backend to the selected DD version
191
138
  idsdef_path = None
@@ -193,7 +140,6 @@ class ALDBEntryImpl(DBEntryImpl):
193
140
  if factory._xml_path is not None:
194
141
  # Factory was constructed with an explicit XML path, point UDA to that:
195
142
  idsdef_path = factory._xml_path
196
-
197
143
  elif "IMAS_PREFIX" in os.environ:
198
144
  # Check if UDA can use the IDSDef.xml stored in $IMAS_PREFIX/include/
199
145
  idsdef_path = os.environ["IMAS_PREFIX"] + "/include/IDSDef.xml"
@@ -203,15 +149,9 @@ class ALDBEntryImpl(DBEntryImpl):
203
149
  if idsdef_path is None:
204
150
  # Extract XML from the DD zip and point UDA to it
205
151
  idsdef_path = extract_idsdef(factory.version)
206
-
207
152
  os.environ["IDSDEF_PATH"] = idsdef_path
208
- logger.warning(
209
- "The UDA backend is not tested with "
210
- "IMAS-Python and may not work properly. "
211
- "Please raise any issues you find."
212
- )
213
153
 
214
- elif backend == "flexbuffers":
154
+ elif backend in ["hdf5", "memory", "ascii", "flexbuffers"]:
215
155
  pass # nothing to set up
216
156
 
217
157
  else:
@@ -249,6 +189,8 @@ class ALDBEntryImpl(DBEntryImpl):
249
189
  raise RuntimeError("Database entry is not open.")
250
190
  if lazy and self.backend == "ascii":
251
191
  raise RuntimeError("Lazy loading is not supported by the ASCII backend.")
192
+ if self.backend == "uda":
193
+ self._check_uda_warnings(lazy)
252
194
 
253
195
  # Mixing contexts can be problematic, ensure all lazy contexts are closed:
254
196
  self._clear_lazy_ctx_cache()
@@ -339,6 +281,15 @@ class ALDBEntryImpl(DBEntryImpl):
339
281
  # Create a version conversion map, if needed
340
282
  nbc_map = None
341
283
  if ids._version != self._ids_factory._version:
284
+ if ids._version.split(".")[0] != self._ids_factory._version.split(".")[0]:
285
+ raise RuntimeError(
286
+ f"Provided IDS uses DD {ids._version} which has a different major "
287
+ f"version than the Data Entry ({self._ids_factory._version}). "
288
+ "IMAS-Python will not automatically convert this data for you."
289
+ "See the documentation for more details and fixes: "
290
+ f"{imas.PUBLISHED_DOCUMENTATION_ROOT}"
291
+ "/multi-dd.html#storing-idss-with-a-different-major-version"
292
+ )
342
293
  ddmap, source_is_older = dd_version_map_from_factories(
343
294
  ids_name, ids._parent, self._ids_factory
344
295
  )
@@ -405,3 +356,28 @@ class ALDBEntryImpl(DBEntryImpl):
405
356
  "Access Layer 5.1 or newer is required."
406
357
  ) from None
407
358
  return occurrence_list
359
+
360
+ def _check_uda_warnings(self, lazy: bool) -> None:
361
+ """Various checks / warnings for the UDA backend."""
362
+ cache_mode = self._querydict.get("cache_mode")
363
+ if lazy and cache_mode != "none":
364
+ # cache_mode=none requires imas core 5.5.1 or newer, and a recent enough UDA
365
+ # server plugin (which we cannot check...)
366
+ cache_mode_hint = ""
367
+ if ll_interface._al_version >= Version("5.5.1"):
368
+ cache_mode_hint = (
369
+ "\nYou may add the parameter ';cache_mode=none' to the IMAS URI "
370
+ "to avoid loading all of the data from the remote server."
371
+ )
372
+ logger.warning(
373
+ "The UDA backend will load all IDS data from the remote server. "
374
+ "Lazy loading with the UDA backend may therefore still be slow.%s",
375
+ cache_mode_hint,
376
+ )
377
+
378
+ if cache_mode == "none" and ll_interface._al_version < Version("5.5.1"):
379
+ logger.warning(
380
+ "UDA option 'cache_mode=None' may not work correctly with "
381
+ "IMAS Core version %s.",
382
+ ll_interface._al_version,
383
+ )
@@ -6,6 +6,7 @@ Helper module for providing a version-independent interface to the Access Layer.
6
6
  This module tries to abstract away most API incompatibilities between the supported
7
7
  Access Layer versions (for example the rename of _ual_lowlevel to _al_lowlevel).
8
8
  """
9
+
9
10
  import inspect
10
11
  import logging
11
12
 
@@ -31,7 +32,7 @@ except ImportError as exc:
31
32
  has_imas = False
32
33
  imasdef = None
33
34
  lowlevel = None
34
- logger.critical(
35
+ logger.warning(
35
36
  "Could not import 'imas_core': %s. Some functionality is not available.",
36
37
  exc,
37
38
  )
@@ -61,9 +62,6 @@ class LowlevelInterface:
61
62
 
62
63
  - If the lowlevel drops methods, we need to update the implementation fo the method
63
64
  to provide a proper error message or a workaround.
64
- - Renamed methods (if this will ever happen) are perhaps best handled in the
65
- ``__init__`` by providing a mapping of new to old name, so far this was only
66
- relevant for the ``ual_`` to ``al_`` rename.
67
65
  """
68
66
 
69
67
  def __init__(self, lowlevel):
@@ -84,23 +82,13 @@ class LowlevelInterface:
84
82
  # Introduced after 5.0.0
85
83
  self._al_version_str = self._lowlevel.get_al_version()
86
84
  self._al_version = Version(self._al_version_str)
87
- elif hasattr(lowlevel, "al_read_data"):
88
- # In AL 5.0.0, all `ual_` methods were renamed to `al_`
85
+ else:
89
86
  self._al_version_str = "5.0.0"
90
87
  self._al_version = Version(self._al_version_str)
91
- else:
92
- # AL 4, don't try to determine in more detail
93
- self._al_version_str = "4.?.?"
94
- self._al_version = Version("4")
95
- public_methods.remove("close_pulse")
96
88
 
97
- if self._al_version < Version("5"):
98
- method_prefix = "ual_"
99
- else:
100
- method_prefix = "al_"
101
89
  # Overwrite all of our methods that are implemented in the lowlevel
102
90
  for method in public_methods:
103
- ll_method = getattr(lowlevel, method_prefix + method, None)
91
+ ll_method = getattr(lowlevel, f"al_{method}", None)
104
92
  if ll_method is not None:
105
93
  setattr(self, method, ll_method)
106
94
 
@@ -115,24 +103,10 @@ class LowlevelInterface:
115
103
  f"but the current version is {self._al_version_str}"
116
104
  )
117
105
 
118
- # AL 4 lowlevel API
119
-
120
- def begin_pulse_action(self, backendID, shot, run, user, tokamak, version):
121
- # Removed in AL5, compatibility handled in DBEntry
122
- raise LLInterfaceError(f"{__name__} is not implemented")
123
-
124
- def open_pulse(self, pulseCtx, mode, options):
125
- # Removed in AL5, compatibility handled in DBEntry
126
- raise LLInterfaceError(f"{__name__} is not implemented")
127
-
128
106
  def close_pulse(self, pulseCtx, mode):
129
- # options argument (mandatory in AL4) was removed in AL5
130
- # This method is overwritten in AL5, but for AL4 we need to do this:
131
- return lowlevel.ual_close_pulse(pulseCtx, mode, None)
107
+ raise LLInterfaceError(f"{__name__} is not implemented")
132
108
 
133
- def begin_global_action(self, pulseCtx, dataobjectname, rwmode, datapath=""):
134
- # datapath was added in AL5 to support more efficient partial_get in the
135
- # UDA backend. TODO: figure out if this is useful for lazy loading.
109
+ def begin_global_action(self, pulseCtx, dataobjectname, rwmode, datapath):
136
110
  raise LLInterfaceError(f"{__name__} is not implemented")
137
111
 
138
112
  def begin_slice_action(self, pulseCtx, dataobjectname, rwmode, time, interpmode):
@@ -364,25 +364,3 @@ def jTraverser_jar() -> Path:
364
364
  return jar_path
365
365
  else:
366
366
  raise MDSPlusModelError("jTraverser.jar not found. Is MDSplus-Java available?")
367
-
368
-
369
- def ensure_data_dir(user: str, tokamak: str, version: str, run: int) -> None:
370
- """Ensure that a data dir exists with a similar algorithm that
371
- the MDSplus backend uses to set the data path.
372
- See also mdsplus_backend.cpp:751 (setDataEnv)"""
373
- if user == "public":
374
- if "IMAS_HOME" not in os.environ:
375
- raise RuntimeError(
376
- "Environment variable IMAS_HOME must be set to access "
377
- "the public database."
378
- )
379
- dbdir = Path(os.environ["IMAS_HOME"]) / "shared" / "imasdb" / tokamak / version
380
- elif user[0] == "/":
381
- dbdir = Path(user) / tokamak / version
382
- else:
383
- dbdir = Path.home() / "public" / "imasdb" / tokamak / version
384
-
385
- # Check subfolder based on run
386
- assert 0 <= run <= 99_999
387
- index = run // 10_000
388
- (dbdir / str(index)).mkdir(parents=True, exist_ok=True)
@@ -366,9 +366,12 @@ class LazyContext:
366
366
 
367
367
  if value is not None:
368
368
  if isinstance(value, np.ndarray):
369
- # Convert the numpy array to a read-only view
370
- value = value.view()
371
- value.flags.writeable = False
369
+ if value.ndim == 0: # Unpack 0D numpy arrays:
370
+ value = value.item()
371
+ else:
372
+ # Convert the numpy array to a read-only view
373
+ value = value.view()
374
+ value.flags.writeable = False
372
375
  # NOTE: bypassing IDSPrimitive.value.setter logic
373
376
  child._IDSPrimitive__value = value
374
377
 
imas/command/cli.py CHANGED
@@ -102,7 +102,6 @@ def print_ids(uri, ids, occurrence, print_all):
102
102
  ids Name of the IDS to print (e.g. "core_profiles").
103
103
  occurrence Which occurrence to print (defaults to 0).
104
104
  """
105
- min_version_guard(Version("5.0"))
106
105
  setup_rich_log_handler(False)
107
106
 
108
107
  with DBEntry(uri, "r") as dbentry:
@@ -7,6 +7,8 @@ import platform
7
7
  import re
8
8
  import readline
9
9
  import sys
10
+ from csv import writer as csvwriter
11
+ from collections import Counter, defaultdict
10
12
  from dataclasses import dataclass, field
11
13
  from pathlib import Path
12
14
  from typing import Dict, Iterable, List, Optional
@@ -139,12 +141,70 @@ def ids_info(idsfile: Path):
139
141
  }
140
142
 
141
143
 
144
+ @dataclass
145
+ class _PathUsage:
146
+ num_occurrences: int = 0
147
+ path_counter: Counter = field(default_factory=Counter)
148
+
149
+
150
+ def _write_usage_stats_to_csv(
151
+ writer, usage_per_entry, usage_per_occurrence, num_entries
152
+ ):
153
+ """Write usage statistics to csv file.
154
+
155
+ Args:
156
+ writer: an instance of csv.writer
157
+ usage_per_entry: path usage statistics per data entry
158
+ usage_per_occurrence: path usage statistics per occurrence
159
+ num_entries: number of data entries
160
+ """
161
+ # Write header
162
+ writer.writerow(
163
+ [
164
+ "IDS",
165
+ "Path in IDS",
166
+ "Uses errorbar",
167
+ "Frequency (without occurrences)",
168
+ "Frequency (with occurences)",
169
+ ]
170
+ )
171
+
172
+ for ids_name in sorted(usage_per_entry):
173
+ entry_usage = usage_per_entry[ids_name]
174
+ occurrence_usage = usage_per_occurrence[ids_name]
175
+
176
+ # Usage statistics of the IDS (# entries with this IDS / # entries)
177
+ freq = entry_usage.num_occurrences / num_entries
178
+ writer.writerow([ids_name, "", "", freq, ""])
179
+
180
+ for path, entry_count in sorted(entry_usage.path_counter.items()):
181
+ if "_error_" in path:
182
+ continue # Skip error nodes
183
+ occurrence_count = occurrence_usage.path_counter[path]
184
+
185
+ uses_error = f"{path}_error_upper" in entry_usage.path_counter
186
+ # Frequency without occurrences, see GH#84 for details
187
+ freq1 = entry_count / entry_usage.num_occurrences
188
+ # Frequency with occurences
189
+ freq2 = occurrence_count / occurrence_usage.num_occurrences
190
+
191
+ # Write data row
192
+ writer.writerow([ids_name, path, "X" if uses_error else "", freq1, freq2])
193
+
194
+
195
+ _csv_help_text = (
196
+ "Write analysis output to the provided CSV file. For details, "
197
+ "see https://github.com/iterorganization/IMAS-Python/issues/84."
198
+ )
199
+
200
+
142
201
  @click.command("process-db-analysis")
143
202
  @click.argument(
144
203
  "infiles", metavar="INPUT_FILES...", nargs=-1, type=infile_path, required=True
145
204
  )
146
205
  @click.option("--show-empty-ids", is_flag=True, help="Show empty IDSs in the overview.")
147
- def process_db_analysis(infiles, show_empty_ids):
206
+ @click.option("--csv", type=outfile_path, help=_csv_help_text)
207
+ def process_db_analysis(infiles, show_empty_ids, csv):
148
208
  """Process supplied Data Entry analyses, and display statistics.
149
209
 
150
210
  \b
@@ -153,9 +213,10 @@ def process_db_analysis(infiles, show_empty_ids):
153
213
  """
154
214
  setup_rich_log_handler(False)
155
215
 
156
- factory = imas.IDSFactory()
157
- filled_per_ids = {ids_name: set() for ids_name in factory.ids_names()}
158
- logger.info("Using Data Dictionary version %s.", factory.dd_version)
216
+ usage_per_entry = defaultdict(_PathUsage)
217
+ usage_per_occurrence = defaultdict(_PathUsage)
218
+ num_entries = 0
219
+
159
220
  logger.info("Reading %d input files...", len(infiles))
160
221
 
161
222
  # Read input data and collate usage info per IDS
@@ -164,17 +225,51 @@ def process_db_analysis(infiles, show_empty_ids):
164
225
  data = json.load(file)
165
226
 
166
227
  for entry in data:
228
+ usage_for_this_entry = defaultdict(_PathUsage)
167
229
  for ids_info in entry["ids_info"]:
168
- fill_info = filled_per_ids[ids_info["name"]]
169
- fill_info.update(ids_info["filled_data"])
230
+ ids_name = ids_info["name"]
231
+ filled_paths = ids_info["filled_data"]
232
+ # Update counters for this entry
233
+ usage_for_this_entry[ids_name].path_counter.update(filled_paths)
234
+ # Update counters for all occurrecnes
235
+ usage_per_occurrence[ids_name].num_occurrences += 1
236
+ usage_per_occurrence[ids_name].path_counter.update(filled_paths)
237
+ # Update data entry usage
238
+ for ids_name, usage in usage_for_this_entry.items():
239
+ usage_per_entry[ids_name].num_occurrences += 1
240
+ usage_per_entry[ids_name].path_counter.update(usage.path_counter.keys())
241
+ num_entries += 1
170
242
 
171
243
  logger.info("Done reading input files.")
244
+
245
+ if csv is not None:
246
+ # Output to CSV file
247
+ logger.info("Writing output to CSV file: %s", csv)
248
+ with open(csv, "w") as csvfile:
249
+ writer = csvwriter(csvfile)
250
+ _write_usage_stats_to_csv(
251
+ writer, usage_per_entry, usage_per_occurrence, num_entries
252
+ )
253
+ logger.info("Done.")
254
+ return
255
+
172
256
  logger.info("Analyzing filled data...")
257
+ factory = imas.IDSFactory()
258
+ logger.info("Using Data Dictionary version %s.", factory.dd_version)
173
259
 
174
260
  # Construct AnalysisNodes per IDS
175
261
  analysis_nodes: Dict[str, _AnalysisNode] = {}
176
- for ids_name, filled in filled_per_ids.items():
262
+ for ids_name, usage in usage_per_occurrence.items():
263
+ if ids_name not in factory.ids_names():
264
+ logger.warning(
265
+ "Founds IDS %s in data files, but this IDS is not available "
266
+ "in DD version %s. Statistics will not be tracked.",
267
+ ids_name,
268
+ factory.dd_version,
269
+ )
270
+ continue
177
271
  metadata = factory.new(ids_name).metadata
272
+ filled = set(usage.path_counter.keys())
178
273
  ids_analysis_node = _AnalysisNode("")
179
274
 
180
275
  def walk_metadata_tree(metadata: IDSMetadata, node: _AnalysisNode):
imas/command/helpers.py CHANGED
@@ -36,7 +36,7 @@ def min_version_guard(al_version: Version):
36
36
  al_version: Minimum imas_core version required for this command.
37
37
  """
38
38
  used_version = ll_interface._al_version
39
- if used_version >= al_version:
39
+ if used_version and used_version >= al_version:
40
40
  return
41
41
  click.echo(
42
42
  f"This command requires at least version {al_version} of the Access Layer."