tecio-python 0.3.1__tar.gz → 0.3.3__tar.gz

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 (47) hide show
  1. {tecio_python-0.3.1/tecio_python.egg-info → tecio_python-0.3.3}/PKG-INFO +8 -6
  2. {tecio_python-0.3.1 → tecio_python-0.3.3}/README.md +7 -5
  3. tecio_python-0.3.3/plugins/matlab/fecplot.m +111 -0
  4. {tecio_python-0.3.1/tecio → tecio_python-0.3.3}/plugins/paraview/TecplotTecioReader.py +14 -5
  5. {tecio_python-0.3.1 → tecio_python-0.3.3}/pyproject.toml +6 -2
  6. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/__init__.py +24 -2
  7. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_containers.py +21 -21
  8. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_dat_read.py +437 -68
  9. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_dat_write.py +149 -270
  10. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_io.py +275 -24
  11. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_meta.py +28 -15
  12. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_plt_read.py +171 -17
  13. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_plt_write.py +79 -282
  14. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_reader.py +230 -11
  15. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_szl_read.py +37 -4
  16. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_szl_write.py +129 -341
  17. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_utils.py +3 -1
  18. tecio_python-0.3.3/tecio/_writer.py +953 -0
  19. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tec2mat.py +20 -4
  20. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecaux.py +20 -10
  21. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecdump.py +33 -18
  22. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecextract.py +131 -35
  23. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecfix.py +22 -8
  24. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecmerge.py +361 -50
  25. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/teconvert.py +17 -7
  26. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecscale.py +34 -23
  27. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecslice.py +27 -10
  28. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/tecstats.py +116 -50
  29. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/libtecio.py +712 -137
  30. {tecio_python-0.3.1 → tecio_python-0.3.3/tecio_python.egg-info}/PKG-INFO +8 -6
  31. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio_python.egg-info/SOURCES.txt +2 -2
  32. {tecio_python-0.3.1 → tecio_python-0.3.3}/tests/test_cli.py +130 -130
  33. {tecio_python-0.3.1 → tecio_python-0.3.3}/tests/test_io.py +212 -34
  34. {tecio_python-0.3.1 → tecio_python-0.3.3}/tests/test_read.py +99 -41
  35. {tecio_python-0.3.1 → tecio_python-0.3.3}/tests/test_write.py +371 -117
  36. tecio_python-0.3.1/tecio/_writer.py +0 -331
  37. tecio_python-0.3.1/tecio/old_libtecio.py +0 -3689
  38. {tecio_python-0.3.1 → tecio_python-0.3.3}/LICENSE +0 -0
  39. {tecio_python-0.3.1 → tecio_python-0.3.3}/NOTICE +0 -0
  40. {tecio_python-0.3.1 → tecio_python-0.3.3}/setup.cfg +0 -0
  41. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/_constants.py +0 -0
  42. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio/cli/__init__.py +0 -0
  43. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio_python.egg-info/dependency_links.txt +0 -0
  44. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio_python.egg-info/entry_points.txt +0 -0
  45. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio_python.egg-info/requires.txt +0 -0
  46. {tecio_python-0.3.1 → tecio_python-0.3.3}/tecio_python.egg-info/top_level.txt +0 -0
  47. {tecio_python-0.3.1 → tecio_python-0.3.3}/tests/test_libtecio.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tecio-python
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Python interface for reading and writing Tecplot data files
5
5
  Project-URL: Homepage, https://github.com/meersman/tecio
6
6
  Project-URL: Documentation, https://meersman.github.io/tecio/
@@ -82,6 +82,8 @@ export TECIO_LIB=/path/to/libtecio.dylib # macOS
82
82
 
83
83
  ## Quick start
84
84
 
85
+ Writing a file:
86
+
85
87
  ```python
86
88
  import tecio
87
89
  import numpy as np
@@ -90,7 +92,7 @@ x = np.linspace(0, 2 * np.pi, 100)
90
92
  y = np.sin(x)
91
93
 
92
94
  with tecio.open("sine.szplt", "w") as tec:
93
- tec.write_ijk_zone(data=[x, y], variables=["x", "y"])
95
+ tec.write_ordered_zone(data=[x, y], variables=["x", "y"])
94
96
  ```
95
97
 
96
98
  Reading a file:
@@ -98,10 +100,10 @@ Reading a file:
98
100
  ```python
99
101
  with tecio.open("sine.szplt", "r") as tec:
100
102
  print(tec.variables) # ['x', 'y']
101
- x = tec.zone[0].variable[0].values
102
- y = tec.zone[0].variable[1].values
103
+ x = tec.zones[0].variables[0].values
104
+ y = tec.zones[0].variables[1].values
103
105
  # or
104
- x, y = tec.zone[0].get_array(["x", "y"])
106
+ x, y = tec.zones[0].get_array(["x", "y"])
105
107
  ```
106
108
 
107
109
  ## API
@@ -129,7 +131,7 @@ Supported formats:
129
131
 
130
132
  ## Contributing
131
133
 
132
- Contributions are welcome see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
134
+ Contributions are welcome (see [CONTRIBUTING.md](CONTRIBUTING.md) for details).
133
135
 
134
136
  ---
135
137
 
@@ -38,6 +38,8 @@ export TECIO_LIB=/path/to/libtecio.dylib # macOS
38
38
 
39
39
  ## Quick start
40
40
 
41
+ Writing a file:
42
+
41
43
  ```python
42
44
  import tecio
43
45
  import numpy as np
@@ -46,7 +48,7 @@ x = np.linspace(0, 2 * np.pi, 100)
46
48
  y = np.sin(x)
47
49
 
48
50
  with tecio.open("sine.szplt", "w") as tec:
49
- tec.write_ijk_zone(data=[x, y], variables=["x", "y"])
51
+ tec.write_ordered_zone(data=[x, y], variables=["x", "y"])
50
52
  ```
51
53
 
52
54
  Reading a file:
@@ -54,10 +56,10 @@ Reading a file:
54
56
  ```python
55
57
  with tecio.open("sine.szplt", "r") as tec:
56
58
  print(tec.variables) # ['x', 'y']
57
- x = tec.zone[0].variable[0].values
58
- y = tec.zone[0].variable[1].values
59
+ x = tec.zones[0].variables[0].values
60
+ y = tec.zones[0].variables[1].values
59
61
  # or
60
- x, y = tec.zone[0].get_array(["x", "y"])
62
+ x, y = tec.zones[0].get_array(["x", "y"])
61
63
  ```
62
64
 
63
65
  ## API
@@ -85,7 +87,7 @@ Supported formats:
85
87
 
86
88
  ## Contributing
87
89
 
88
- Contributions are welcome see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
90
+ Contributions are welcome (see [CONTRIBUTING.md](CONTRIBUTING.md) for details).
89
91
 
90
92
  ---
91
93
 
@@ -0,0 +1,111 @@
1
+ function f = fecplot(dim, x, y, z, c, node_map, varargin)
2
+ % Filled patch contour for unstructured 2D/3D surface zones
3
+ %
4
+ % Call:
5
+ % h = feplot(dim, x, y, z, c, node_map, ...)
6
+ %
7
+ % Args:
8
+ % dim: 2 or 3
9
+ % x,y[,z]: nodal coordinate column vectors (Nx1)
10
+ % c: contour values; either nodal (Nx1) or cell-centered (Mcells x 1)
11
+ % node_map: M x K integer matrix of node indices per cell (K=3 or 4)
12
+ % varargin: name-value pairs forwarded to patch (e.g. 'EdgeColor','none')
13
+ %
14
+ % Returns:
15
+ % f: handle to the patch object
16
+ %
17
+ % Notes:
18
+ % - If c is nodal, uses 'FaceVertexCData' with 'FaceColor'='interp'
19
+ % - If c is cell-centered, uses 'CData' per-face with 'FaceColor'='flat'
20
+ % - For dim==3, z must be provided and plotting uses 3D patch (viewable with lighting)
21
+ % - node_map may contain NaNs for unequal-sided cells; those faces are handled
22
+
23
+ % Validate dim
24
+ if ~ismember(dim, [2,3])
25
+ error('dim must be 2 or 3');
26
+ end
27
+
28
+ % Ensure column vectors
29
+ x = x(:); y = y(:);
30
+ if dim == 3
31
+ if nargin < 4 || isempty(z)
32
+ error('z must be provided for dim==3');
33
+ end
34
+ z = z(:);
35
+ end
36
+
37
+ % Determine number of nodes and faces
38
+ nNodes = numel(x);
39
+ [nFaces, ~] = size(node_map);
40
+
41
+ % Validate node_map indices
42
+ if any(node_map(:) > nNodes) || any(node_map(:) < 0 & ~isnan(node_map(:)))
43
+ error('node_map contains invalid node indices.');
44
+ end
45
+
46
+ % Prepare vertices matrix
47
+ if dim == 2
48
+ verts = [x, y];
49
+ else
50
+ verts = [x, y, z];
51
+ end
52
+
53
+ % Determine if c is nodal or cell-centered
54
+ c = c(:);
55
+ isNodal = (numel(c) == nNodes);
56
+ isCell = (numel(c) == nFaces);
57
+ if ~(isNodal || isCell)
58
+ error('Length of c must match number of nodes (nodal) or number of faces (cell-centered).');
59
+ end
60
+
61
+ % Clean node_map: replace zeros -> NaN (if any), ensure double
62
+ node_map = double(node_map);
63
+ node_map(node_map==0) = NaN;
64
+
65
+ % If node_map contains NaNs for some cells, patch accepts NaN-separated faces.
66
+ % Create faces in appropriate format: patch accepts face vertex indices matrix.
67
+ faces = node_map;
68
+
69
+ % Build patch arguments
70
+ pArgs = varargin;
71
+
72
+ if isNodal
73
+ % Nodal data: use FaceVertexCData with interpolation
74
+ % Create patch with vertices and faces, supply FaceVertexCData
75
+ % Set FaceColor to 'interp' and EdgeColor as provided or 'none' by default
76
+ if ~any(strcmpi('FaceColor',pArgs))
77
+ pArgs = [{'FaceColor','interp'}, pArgs];
78
+ end
79
+ if ~any(strcmpi('EdgeColor',pArgs))
80
+ pArgs = [{'EdgeColor','none'}, pArgs];
81
+ end
82
+ % Create patch
83
+ f = patch('Vertices', verts, 'Faces', faces, 'FaceVertexCData', c, pArgs{:});
84
+
85
+ else
86
+ % Cell-centered: supply per-face CData and use flat coloring
87
+ if ~any(strcmpi('FaceColor',pArgs))
88
+ pArgs = [{'FaceColor','flat'}, pArgs];
89
+ end
90
+ if ~any(strcmpi('EdgeColor',pArgs))
91
+ pArgs = [{'EdgeColor','none'}, pArgs];
92
+ end
93
+ % patch expects CData as Mx1 or Mx3 color; provide as Mx1
94
+ faceCData = c;
95
+ % Create patch
96
+ f = patch('Vertices', verts, 'Faces', faces, 'CData', faceCData, pArgs{:});
97
+
98
+ end
99
+
100
+ % Set colormap and colorbar behavior consistent with contourf
101
+ if ~any(strcmpi('EdgeColor',pArgs))
102
+ set(f,'EdgeColor','none');
103
+ end
104
+ axis equal
105
+ if dim == 3
106
+ view(3)
107
+ camlight headlight
108
+ lighting gouraud
109
+ end
110
+ colorbar
111
+ end
@@ -170,6 +170,9 @@ _AXIS_SYNONYMS: dict[str, frozenset[str]] = {
170
170
  "coordx",
171
171
  "xcoord",
172
172
  "x_coord",
173
+ "xgrid",
174
+ "x-grid",
175
+ "x_grid",
173
176
  }),
174
177
  "y": frozenset({
175
178
  "y",
@@ -180,6 +183,9 @@ _AXIS_SYNONYMS: dict[str, frozenset[str]] = {
180
183
  "coordy",
181
184
  "ycoord",
182
185
  "y_coord",
186
+ "ygrid",
187
+ "y-grid",
188
+ "y_grid",
183
189
  }),
184
190
  "z": frozenset({
185
191
  "z",
@@ -190,6 +196,9 @@ _AXIS_SYNONYMS: dict[str, frozenset[str]] = {
190
196
  "coordz",
191
197
  "zcoord",
192
198
  "z_coord",
199
+ "zgrid",
200
+ "z-grid",
201
+ "z_grid",
193
202
  }),
194
203
  }
195
204
 
@@ -449,7 +458,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
449
458
 
450
459
  self._zone_selection.RemoveAllArrays()
451
460
  self._zone_keys.clear()
452
- for zone in reader.zone:
461
+ for zone in reader.zones:
453
462
  title = zone.title or f"Zone {zone.zone_index}"
454
463
  key = f"{zone.zone_index}: {title}"
455
464
  self._zone_keys[zone.zone_index] = key
@@ -677,7 +686,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
677
686
  static/always-present geometry, so they're excluded from the time axis and
678
687
  instead included at every requested time (see :meth:`RequestData`).
679
688
  """
680
- times = {zone.solution_time for zone in reader.zone if zone.strand_id != 0}
689
+ times = {zone.solution_time for zone in reader.zones if zone.strand_id != 0}
681
690
  return sorted(times)
682
691
 
683
692
  @smproperty.doublevector(
@@ -760,7 +769,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
760
769
  self._add_variable_aux_field_data(output.GetFieldData(), reader)
761
770
 
762
771
  block_index = 0
763
- for zone in reader.zone:
772
+ for zone in reader.zones:
764
773
  key = self._zone_keys.get(zone.zone_index)
765
774
  if key is not None and not self._zone_selection.ArrayIsEnabled(key):
766
775
  continue
@@ -990,7 +999,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
990
999
  n_points = dataset.GetNumberOfPoints()
991
1000
  n_cells = dataset.GetNumberOfCells()
992
1001
 
993
- for var in zone.variable:
1002
+ for var in zone.variables:
994
1003
  if not self._array_selection.ArrayIsEnabled(var.name):
995
1004
  continue
996
1005
  values = var.values
@@ -1039,7 +1048,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
1039
1048
  if name is None:
1040
1049
  flat_components.append(None)
1041
1050
  continue
1042
- var = zone.variable[name]
1051
+ var = zone.variables[name]
1043
1052
  values = var.values
1044
1053
  if values is None: # passive, or absent in this specific zone
1045
1054
  flat_components.append(None)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tecio-python"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "Python interface for reading and writing Tecplot data files"
9
9
 
10
10
  readme = "README.md"
@@ -69,6 +69,10 @@ mat = ["scipy"]
69
69
  include = ["tecio*"]
70
70
  exclude = ["archive*", "test*"]
71
71
 
72
+ [tool.setuptools.data-files]
73
+ "share/plugins/paraview" = ["plugins/paraview/*"]
74
+ "share/plugins/matlab" = ["plugins/matlab/*"]
75
+
72
76
  [tool.ruff]
73
77
  line-length = 88
74
78
  target-version = "py310"
@@ -109,7 +113,7 @@ python-version = "3.11"
109
113
  output-format = "concise"
110
114
 
111
115
  [[tool.ty.overrides]]
112
- include = ["tecio/plugins/**"]
116
+ include = ["plugins/**"]
113
117
 
114
118
  [tool.ty.overrides.rules]
115
119
  unresolved-import = "ignore"
@@ -9,7 +9,7 @@ from importlib import metadata
9
9
  try:
10
10
  __version__ = metadata.version("tecio")
11
11
  except metadata.PackageNotFoundError:
12
- __version__ = "0.3.1"
12
+ __version__ = "0.3.3"
13
13
 
14
14
  from . import cli, libtecio
15
15
  from ._constants import (
@@ -28,7 +28,20 @@ from ._constants import (
28
28
  from ._containers import VariableList, ZoneList
29
29
  from ._dat_read import TecplotDatReader
30
30
  from ._dat_write import TecplotDatWriter
31
- from ._io import AppendReadWrite, AppendWrite, open
31
+ from ._io import (
32
+ AppendReadWrite,
33
+ AppendWrite,
34
+ FileSummary,
35
+ ZoneSummary,
36
+ get_file_type,
37
+ get_num_variables,
38
+ get_num_zones,
39
+ get_title,
40
+ get_variable_list,
41
+ get_zone_list,
42
+ open,
43
+ peek,
44
+ )
32
45
  from ._plt_read import TecplotPltReader
33
46
  from ._plt_write import TecplotPltWriter
34
47
  from ._reader import (
@@ -82,6 +95,15 @@ del _cls
82
95
  __all__ = [
83
96
  "libtecio",
84
97
  "open",
98
+ "peek",
99
+ "get_variable_list",
100
+ "get_zone_list",
101
+ "get_num_zones",
102
+ "get_num_variables",
103
+ "get_title",
104
+ "get_file_type",
105
+ "FileSummary",
106
+ "ZoneSummary",
85
107
  "cli",
86
108
  "AppendWrite",
87
109
  "AppendReadWrite",
@@ -1,35 +1,35 @@
1
1
  """Index- and name-based container types for Tecplot data collections.
2
2
 
3
- These containers are shared by the ``tecio`` readers: ``Read.zone`` returns a
4
- :class:`ZoneList` of ``ReadZone`` and ``ReadZone.variable`` returns a
3
+ These containers are shared by the ``tecio`` readers: ``Read.zones`` returns a
4
+ :class:`ZoneList` of ``ReadZone`` and ``ReadZone.variables`` returns a
5
5
  :class:`VariableList` of ``ReadVariable`` for every supported format (SZL, PLT, DAT).
6
- They depend only on small structural protocols (``.name`` for variables, ``.variable``
6
+ They depend only on small structural protocols (``.name`` for variables, ``.variables``
7
7
  for zones) so they import nothing from either hierarchy and cannot introduce a circular
8
8
  dependency.
9
9
 
10
10
  Access model:
11
11
 
12
- reader.zone # ZoneList
13
- reader.zone[0] # ReadZone (element)
14
- reader.zone[1:4] # ZoneList (sub-collection, same kind)
15
- reader.zone[0].variable # VariableList
16
- reader.zone[0].variable["x"] # ReadVariable (object: .values, .is_passive)
17
- reader.zone[0].variable[2] # ReadVariable (0-based index)
12
+ reader.zones # ZoneList
13
+ reader.zones[0] # ReadZone (element)
14
+ reader.zones[1:4] # ZoneList (sub-collection, same kind)
15
+ reader.zones[0].variables # VariableList
16
+ reader.zones[0].variables["x"] # ReadVariable (object: .values, .is_passive)
17
+ reader.zones[0].variables[2] # ReadVariable (0-based index)
18
18
 
19
19
  Subscripting always returns an element or a sub-collection *of the same kind* (never a
20
20
  raw array). The underlying NumPy data is pulled with ``get_array`` on a single zone,
21
21
  which mirrors the pandas ``df[...]`` split: a scalar key returns one array, a list of
22
22
  names returns a tuple of arrays (for unpacking)::
23
23
 
24
- p = reader.zone[0].get_array("p") # ndarray | None
25
- p = reader.zone[0].get_array(2) # ndarray | None (0-based index)
26
- x, y, z = reader.zone[0].get_array(["x", "y", "z"]) # tuple, one per name
24
+ p = reader.zones[0].get_array("p") # ndarray | None
25
+ p = reader.zones[0].get_array(2) # ndarray | None (0-based index)
26
+ x, y, z = reader.zones[0].get_array(["x", "y", "z"]) # tuple, one per name
27
27
 
28
28
  There is deliberately **no** cross-zone array accessor. To pull one variable across
29
29
  many zones (e.g. a transient sequence), iterate explicitly so the outer axis is owned by
30
30
  your code, and stack only when you know the result is rectangular::
31
31
 
32
- seq = [z.get_array("p") for z in reader.zone] # list[ndarray | None]
32
+ seq = [z.get_array("p") for z in reader.zones] # list[ndarray | None]
33
33
  stack = np.stack(seq) # only if shapes all match
34
34
 
35
35
  Name lookup is exact and case-sensitive throughout, so distinct variables such
@@ -78,7 +78,7 @@ class _HasVariableList(Protocol):
78
78
  """A zone element exposing a name/index-addressable variable container."""
79
79
 
80
80
  @property
81
- def variable(self) -> VariableList[Any]: ...
81
+ def variables(self) -> VariableList[Any]: ...
82
82
 
83
83
 
84
84
  _VarT = TypeVar("_VarT", bound=_HasName)
@@ -137,7 +137,7 @@ def select_variable_arrays(
137
137
  class VariableList(Generic[_VarT]):
138
138
  """Read-only sequence of variables with positional *and* named access.
139
139
 
140
- Drop-in for the ``list`` previously returned by ``ReadZone.variable``:
140
+ Drop-in for the ``list`` previously returned by ``ReadZone.variables``:
141
141
  iteration, ``len()``, and integer indexing are unchanged. A string key
142
142
  resolves a variable by its exact, case-sensitive name.
143
143
 
@@ -208,7 +208,7 @@ class VariableList(Generic[_VarT]):
208
208
  """Return the variable names in dataset order."""
209
209
  return [var.name for var in self._items]
210
210
 
211
- def __repr__(self) -> str:
211
+ def __repr__(self) -> str: # pragma: no cover
212
212
  n = len(self._items)
213
213
  if n == 0:
214
214
  return "VariableList([])"
@@ -229,16 +229,16 @@ class VariableList(Generic[_VarT]):
229
229
  class ZoneList(Generic[_ZoneT]):
230
230
  """Read-only sequence of zones: positional access and slicing only.
231
231
 
232
- Drop-in for the ``list`` previously returned by ``Read.zone``: iteration, ``len()``,
233
- and integer indexing are unchanged. Slicing returns another :class:`ZoneList` (not a
234
- plain ``list``) so navigation composes.
232
+ Drop-in for the ``list`` previously returned by ``Read.zones``: iteration,
233
+ ``len()``, and integer indexing are unchanged. Slicing returns another
234
+ :class:`ZoneList` (not a plain ``list``) so navigation composes.
235
235
 
236
236
  This container deliberately exposes **no** data-extraction method. Pulling one
237
237
  variable across many zones is an explicit loop over the zones, keeping the outer
238
238
  (zone) axis owned by the caller (see the module docs).
239
239
 
240
240
  Args:
241
- zones: Ordered list of zone elements (each exposing ``.variable``).
241
+ zones: Ordered list of zone elements (each exposing ``.variables``).
242
242
  """
243
243
 
244
244
  __slots__ = ("_items",)
@@ -277,7 +277,7 @@ class ZoneList(Generic[_ZoneT]):
277
277
  return ZoneList(self._items[key])
278
278
  return self._items[key]
279
279
 
280
- def __repr__(self) -> str:
280
+ def __repr__(self) -> str: # pragma: no cover
281
281
  n = len(self._items)
282
282
  if n == 0:
283
283
  return "ZoneList([])"