mt-metadata 0.3.6__py2.py3-none-any.whl → 0.3.8__py2.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.

Potentially problematic release.


This version of mt-metadata might be problematic. Click here for more details.

Files changed (32) hide show
  1. mt_metadata/__init__.py +1 -1
  2. mt_metadata/base/helpers.py +9 -2
  3. mt_metadata/timeseries/filters/filtered.py +133 -75
  4. mt_metadata/timeseries/station.py +31 -0
  5. mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py +1 -0
  6. mt_metadata/transfer_functions/__init__.py +38 -0
  7. mt_metadata/transfer_functions/core.py +7 -8
  8. mt_metadata/transfer_functions/io/edi/edi.py +40 -19
  9. mt_metadata/transfer_functions/io/edi/metadata/define_measurement.py +1 -0
  10. mt_metadata/transfer_functions/io/edi/metadata/emeasurement.py +4 -2
  11. mt_metadata/transfer_functions/io/edi/metadata/header.py +3 -1
  12. mt_metadata/transfer_functions/io/edi/metadata/information.py +13 -6
  13. mt_metadata/transfer_functions/io/emtfxml/emtfxml.py +8 -2
  14. mt_metadata/transfer_functions/io/emtfxml/metadata/data.py +1 -1
  15. mt_metadata/transfer_functions/io/emtfxml/metadata/estimate.py +1 -1
  16. mt_metadata/transfer_functions/io/emtfxml/metadata/period_range.py +6 -1
  17. mt_metadata/transfer_functions/io/emtfxml/metadata/provenance.py +6 -2
  18. mt_metadata/transfer_functions/io/emtfxml/metadata/standards/copyright.json +2 -1
  19. mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py +2 -44
  20. mt_metadata/transfer_functions/processing/aurora/decimation_level.py +5 -5
  21. mt_metadata/transfer_functions/processing/aurora/standards/regression.json +46 -1
  22. mt_metadata/transfer_functions/processing/aurora/station.py +17 -11
  23. mt_metadata/transfer_functions/processing/aurora/stations.py +4 -4
  24. mt_metadata/utils/list_dict.py +19 -12
  25. mt_metadata/utils/mttime.py +1 -1
  26. mt_metadata/utils/validators.py +11 -2
  27. {mt_metadata-0.3.6.dist-info → mt_metadata-0.3.8.dist-info}/METADATA +60 -3
  28. {mt_metadata-0.3.6.dist-info → mt_metadata-0.3.8.dist-info}/RECORD +32 -32
  29. {mt_metadata-0.3.6.dist-info → mt_metadata-0.3.8.dist-info}/AUTHORS.rst +0 -0
  30. {mt_metadata-0.3.6.dist-info → mt_metadata-0.3.8.dist-info}/LICENSE +0 -0
  31. {mt_metadata-0.3.6.dist-info → mt_metadata-0.3.8.dist-info}/WHEEL +0 -0
  32. {mt_metadata-0.3.6.dist-info → mt_metadata-0.3.8.dist-info}/top_level.txt +0 -0
@@ -358,14 +358,20 @@ class EMTFXML(emtf_xml.EMTF):
358
358
  if hasattr(value, "to_xml") and callable(getattr(value, "to_xml")):
359
359
  if key == "processing_info":
360
360
  if skip_field_notes:
361
- value.remote_info._order.remove("field_notes")
361
+ try:
362
+ value.remote_info._order.remove("field_notes")
363
+ except ValueError:
364
+ self.logger.debug("No field notes to skip.")
362
365
  if value.remote_info.site.id in [
363
366
  None,
364
367
  "",
365
368
  "None",
366
369
  "none",
367
370
  ]:
368
- value.remote_info._order.remove("site")
371
+ try:
372
+ value.remote_info._order.remove("site")
373
+ except ValueError:
374
+ self.logger.debug("No remote field notes to skip.")
369
375
  element = value.to_xml()
370
376
  if isinstance(element, list):
371
377
  for item in element:
@@ -432,7 +432,7 @@ class TransferFunction(Base):
432
432
  pass
433
433
 
434
434
  comp_element = et.SubElement(
435
- period_element, key.replace("_", ".").capitalize(), attr_dict
435
+ period_element, key.replace("_", ".").upper(), attr_dict
436
436
  )
437
437
  idx_dict = self.write_dict[key]
438
438
  shape = arr.shape
@@ -56,7 +56,7 @@ class Estimate(Base):
56
56
 
57
57
  root = et.Element(
58
58
  self.__class__.__name__.capitalize(),
59
- {"name": self.name, "type": self.type},
59
+ {"name": self.name.upper(), "type": self.type},
60
60
  )
61
61
 
62
62
  et.SubElement(root, "Description").text = self.description
@@ -20,6 +20,8 @@ from mt_metadata.transfer_functions.io.emtfxml.metadata import helpers
20
20
 
21
21
  # =============================================================================
22
22
  attr_dict = get_schema("period_range", SCHEMA_FN_PATHS)
23
+
24
+
23
25
  # =============================================================================
24
26
  class PeriodRange(Base):
25
27
  __doc__ = write_lines(attr_dict)
@@ -45,7 +47,10 @@ class PeriodRange(Base):
45
47
 
46
48
  root = et.Element(
47
49
  self.__class__.__name__,
48
- {"min": f"{self.min:.9f}", "max": f"{self.max:.9f}"},
50
+ {
51
+ "min": f"{self.min:<16.5E}".strip(),
52
+ "max": f"{self.max:<16.5E}".strip(),
53
+ },
49
54
  )
50
55
  if string:
51
56
  return element_to_string(root)
@@ -11,7 +11,11 @@ Created on Wed Dec 23 21:30:36 2020
11
11
  # =============================================================================
12
12
  # Imports
13
13
  # =============================================================================
14
- from mt_metadata.base.helpers import write_lines, dict_to_xml, element_to_string
14
+ from mt_metadata.base.helpers import (
15
+ write_lines,
16
+ dict_to_xml,
17
+ element_to_string,
18
+ )
15
19
  from mt_metadata.base import get_schema, Base
16
20
  from .standards import SCHEMA_FN_PATHS
17
21
  from . import Person
@@ -40,7 +44,7 @@ class Provenance(Base):
40
44
 
41
45
  @property
42
46
  def create_time(self):
43
- return self._creation_dt.iso_str
47
+ return self._creation_dt.iso_str.split(".")[0]
44
48
 
45
49
  @create_time.setter
46
50
  def create_time(self, dt_str):
@@ -21,7 +21,8 @@
21
21
  "Restricted release",
22
22
  "Paper Citation Required",
23
23
  "Academic Use Only",
24
- "Conditions Apply"
24
+ "Conditions Apply",
25
+ "Data Citation Required"
25
26
  ],
26
27
  "alias": [],
27
28
  "example": "Unrestricted release",
@@ -10,53 +10,11 @@ from mt_metadata.base.helpers import write_lines
10
10
  from mt_metadata.base import get_schema, Base
11
11
  from .standards import SCHEMA_FN_PATHS
12
12
 
13
+ from mt_metadata.transfer_functions import CHANNEL_MAPS
14
+
13
15
  # =============================================================================
14
16
  attr_dict = get_schema("channel_nomenclature", SCHEMA_FN_PATHS)
15
17
 
16
- # Define allowed sets of channel labellings
17
- STANDARD_INPUT_NAMES = [
18
- "hx",
19
- "hy",
20
- ]
21
- STANDARD_OUTPUT_NAMES = [
22
- "ex",
23
- "ey",
24
- "hz",
25
- ]
26
-
27
- def load_channel_maps():
28
- """
29
- :return: Keys are the channel_nomenclature schema keywords.
30
- Values are dictionaries which map the STANDARD_INPUT_NAMES, \
31
- STANDARD_OUTPUT_NAMES to the channel names associated with a given
32
- channel nomenclature
33
- :rtype: dict
34
- """
35
- import json
36
- import pathlib
37
- fn = pathlib.Path(__file__).parent.joinpath("standards", "channel_nomenclatures.json")
38
- with open(fn) as f:
39
- channel_maps = json.loads(f.read())
40
- return channel_maps
41
-
42
- CHANNEL_MAPS = load_channel_maps()
43
-
44
- def get_allowed_channel_names(standard_names):
45
- """
46
- :param standard_names: one of STANDARD_INPUT_NAMES, or STANDARD_OUTPUT_NAMES
47
- :type standard_names: list
48
- :return: allowed_names: list of channel names that are supported
49
- :rtype: list
50
- """
51
- allowed_names = []
52
- for ch in standard_names:
53
- for _, channel_map in CHANNEL_MAPS.items():
54
- allowed_names.append(channel_map[ch])
55
- allowed_names = list(set(allowed_names))
56
- return allowed_names
57
-
58
- ALLOWED_INPUT_CHANNELS = get_allowed_channel_names(STANDARD_INPUT_NAMES)
59
- ALLOWED_OUTPUT_CHANNELS = get_allowed_channel_names(STANDARD_OUTPUT_NAMES)
60
18
 
61
19
  # =============================================================================
62
20
  class ChannelNomenclature(Base):
@@ -31,7 +31,7 @@ attr_dict.add_dict(get_schema("estimator", SCHEMA_FN_PATHS), "estimator")
31
31
  # =============================================================================
32
32
 
33
33
 
34
- def df_from_bands(band_list):
34
+ def df_from_bands(band_list: list) -> pd.DataFrame:
35
35
  """
36
36
  Utility function that transforms a list of bands into a dataframe
37
37
 
@@ -70,13 +70,13 @@ def df_from_bands(band_list):
70
70
  out_df.reset_index(inplace=True, drop=True)
71
71
  return out_df
72
72
 
73
- def get_fft_harmonics(samples_per_window, sample_rate):
73
+ def get_fft_harmonics(samples_per_window: int, sample_rate: float) -> np.ndarray:
74
74
  """
75
75
  Works for odd and even number of points.
76
76
 
77
- Could be midified with kwargs to support one_sided, two_sided, ignore_dc
78
- ignore_nyquist, and etc. Could actally take FrequencyBands as an argument
79
- if we wanted as well.
77
+ Development notes:
78
+ Could be modified with kwargs to support one_sided, two_sided, ignore_dc
79
+ ignore_nyquist, and etc. Consider taking FrequencyBands as an argument.
80
80
 
81
81
  Parameters
82
82
  ----------
@@ -31,5 +31,50 @@
31
31
  "alias": [],
32
32
  "example": "2",
33
33
  "default": 2
34
+ },
35
+ "r0": {
36
+ "type": "float",
37
+ "required": true,
38
+ "style": "number",
39
+ "units": null,
40
+ "description": "The number of standard deviations where the influence function changes from linear to quadratic",
41
+ "options": [],
42
+ "alias": [],
43
+ "example": "1.4",
44
+ "default": 1.5
45
+ },
46
+ "u0": {
47
+ "type": "float",
48
+ "required": true,
49
+ "style": "number",
50
+ "units": null,
51
+ "description": "Control for redescending Huber regression weights.",
52
+ "options": [],
53
+ "alias": [],
54
+ "example": "2.8",
55
+ "default": 2.8
56
+ },
57
+ "tolerance": {
58
+ "type": "float",
59
+ "required": true,
60
+ "style": "number",
61
+ "units": null,
62
+ "description": "Control for convergence of RME algorithm. Lower means more iterations",
63
+ "options": [],
64
+ "alias": [],
65
+ "example": "0.005",
66
+ "default": 0.005
67
+ },
68
+ "verbosity": {
69
+ "type": "int",
70
+ "required": true,
71
+ "style": "number",
72
+ "units": null,
73
+ "description": "Control for logging messages during regression -- Higher means more messages",
74
+ "options": [0, 1, 2],
75
+ "alias": [],
76
+ "example": "1",
77
+ "default": 0
78
+
34
79
  }
35
- }
80
+ }
@@ -17,6 +17,8 @@ from .run import Run
17
17
 
18
18
  # =============================================================================
19
19
  attr_dict = get_schema("station", SCHEMA_FN_PATHS)
20
+
21
+
20
22
  # =============================================================================
21
23
  class Station(Base):
22
24
  __doc__ = write_lines(attr_dict)
@@ -88,8 +90,8 @@ class Station(Base):
88
90
  processing
89
91
 
90
92
  [
91
- "station_id",
92
- "run_id",
93
+ "station",
94
+ "run",
93
95
  "start",
94
96
  "end",
95
97
  "mth5_path",
@@ -106,8 +108,8 @@ class Station(Base):
106
108
  for run in self.runs:
107
109
  for tp in run.time_periods:
108
110
  entry = {
109
- "station_id": self.id,
110
- "run_id": run.id,
111
+ "station": self.id,
112
+ "run": run.id,
111
113
  "start": tp.start,
112
114
  "end": tp.end,
113
115
  "mth5_path": self.mth5_path,
@@ -130,8 +132,8 @@ class Station(Base):
130
132
  set a data frame
131
133
 
132
134
  [
133
- "station_id",
134
- "run_id",
135
+ "station",
136
+ "run",
135
137
  "start",
136
138
  "end",
137
139
  "mth5_path",
@@ -150,15 +152,17 @@ class Station(Base):
150
152
 
151
153
  self.runs = []
152
154
 
153
- self.id = df.station_id.unique()[0]
155
+ self.id = df.station.unique()[0]
154
156
  self.mth5_path = df.mth5_path.unique()[0]
155
157
  self.remote = df.remote.unique()[0]
156
158
 
157
159
  for entry in df.itertuples():
158
160
  try:
159
- r = self.run_dict[entry.run_id]
161
+ r = self.run_dict[entry.run]
160
162
  r.time_periods.append(
161
- TimePeriod(start=entry.start.isoformat(), end=entry.end.isoformat())
163
+ TimePeriod(
164
+ start=entry.start.isoformat(), end=entry.end.isoformat()
165
+ )
162
166
  )
163
167
 
164
168
  except KeyError:
@@ -167,7 +171,7 @@ class Station(Base):
167
171
  else:
168
172
  channel_scale_factors = {}
169
173
  r = Run(
170
- id=entry.run_id,
174
+ id=entry.run,
171
175
  sample_rate=entry.sample_rate,
172
176
  input_channels=entry.input_channels,
173
177
  output_channels=entry.output_channels,
@@ -175,6 +179,8 @@ class Station(Base):
175
179
  )
176
180
 
177
181
  r.time_periods.append(
178
- TimePeriod(start=entry.start.isoformat(), end=entry.end.isoformat())
182
+ TimePeriod(
183
+ start=entry.start.isoformat(), end=entry.end.isoformat()
184
+ )
179
185
  )
180
186
  self.runs.append(r)
@@ -117,14 +117,14 @@ class Stations(Base):
117
117
 
118
118
  """
119
119
 
120
- station = df[df.remote == False].station_id.unique()[0]
121
- rr_stations = df[df.remote == True].station_id.unique()
120
+ station = df[df.remote == False].station.unique()[0]
121
+ rr_stations = df[df.remote == True].station.unique()
122
122
 
123
- self.local.from_dataset_dataframe(df[df.station_id == station])
123
+ self.local.from_dataset_dataframe(df[df.station == station])
124
124
 
125
125
  for rr_station in rr_stations:
126
126
  rr = Station()
127
- rr.from_dataset_dataframe(df[df.station_id == rr_station])
127
+ rr.from_dataset_dataframe(df[df.station == rr_station])
128
128
  self.add_remote(rr)
129
129
 
130
130
  def to_dataset_dataframe(self):
@@ -44,18 +44,14 @@ class ListDict:
44
44
 
45
45
  def _get_key_from_index(self, index):
46
46
  try:
47
- return next(
48
- key for ii, key in enumerate(self._home) if ii == index
49
- )
47
+ return next(key for ii, key in enumerate(self._home) if ii == index)
50
48
 
51
49
  except StopIteration:
52
50
  raise KeyError(f"Could not find {index}")
53
51
 
54
52
  def _get_index_from_key(self, key):
55
53
  try:
56
- return next(
57
- index for index, k in enumerate(self._home) if k == key
58
- )
54
+ return next(index for index, k in enumerate(self._home) if k == key)
59
55
 
60
56
  except StopIteration:
61
57
  raise KeyError(f"Could not find {key}")
@@ -75,9 +71,7 @@ class ListDict:
75
71
  elif hasattr(obj, "component"):
76
72
  return obj.component
77
73
  else:
78
- raise TypeError(
79
- "could not identify an appropriate key from object"
80
- )
74
+ raise TypeError("could not identify an appropriate key from object")
81
75
 
82
76
  def __deepcopy__(self, memodict={}):
83
77
  """
@@ -230,9 +224,7 @@ class ListDict:
230
224
  raise (KeyError("Could not find None in keys."))
231
225
 
232
226
  else:
233
- raise TypeError(
234
- "could not identify an appropriate key from object"
235
- )
227
+ raise TypeError("could not identify an appropriate key from object")
236
228
 
237
229
  def extend(self, other, skip_keys=[]):
238
230
  """
@@ -282,3 +274,18 @@ class ListDict:
282
274
  )
283
275
 
284
276
  self._home.update(other)
277
+
278
+ def pop(self, key):
279
+ """
280
+ pop item off of dictionary. The key must be verbatim
281
+
282
+ :param key: key of item to be popped off of dictionary
283
+ :type key: string
284
+ :return: item popped
285
+
286
+ """
287
+
288
+ if key in self.keys():
289
+ return dict([self._home.popitem(key)])
290
+ else:
291
+ raise KeyError(f"{key} is not in ListDict keys.")
@@ -400,7 +400,7 @@ class MTime:
400
400
 
401
401
  """
402
402
  t_min_max = False
403
- if dt_str in [None, "", "none", "None", "NONE"]:
403
+ if dt_str in [None, "", "none", "None", "NONE", "Na"]:
404
404
  self.logger.debug(
405
405
  "Time string is None, setting to 1980-01-01:00:00:00"
406
406
  )
@@ -378,6 +378,15 @@ def validate_default(value_dict):
378
378
 
379
379
  def validate_value_type(value, v_type, style=None):
380
380
  """
381
+
382
+ :param value:
383
+ :type value:
384
+ :param v_type:
385
+ :type v_type:
386
+ :param style:
387
+ :type style:
388
+ :return:
389
+
381
390
  validate type from standards
382
391
 
383
392
  """
@@ -469,7 +478,7 @@ def validate_value_type(value, v_type, style=None):
469
478
 
470
479
  # if a number convert to appropriate type
471
480
  elif isinstance(
472
- value, (float, np.float_, np.float16, np.float32, np.float64)
481
+ value, (float, np.float16, np.float32, np.float64)
473
482
  ):
474
483
  if v_type is int:
475
484
  return int(value)
@@ -481,7 +490,7 @@ def validate_value_type(value, v_type, style=None):
481
490
  elif isinstance(value, Iterable):
482
491
  if v_type is str:
483
492
  if isinstance(value, np.ndarray):
484
- value = value.astype(np.unicode_)
493
+ value = value.astype(np.str_)
485
494
  value = [
486
495
  f"{v}".replace("'", "").replace('"', "") for v in value
487
496
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mt-metadata
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: Metadata for magnetotelluric data
5
5
  Home-page: https://github.com/kujaku11/mt_metadata
6
6
  Author: Jared Peacock
@@ -28,7 +28,7 @@ Requires-Dist: matplotlib
28
28
  Requires-Dist: xarray
29
29
  Requires-Dist: loguru
30
30
 
31
- # mt_metadata version 0.3.6
31
+ # mt_metadata version 0.3.8
32
32
  Standard MT metadata
33
33
 
34
34
  [![PyPi version](https://img.shields.io/pypi/v/mt_metadata.svg)](https://pypi.python.org/pypi/mt-metadata)
@@ -58,7 +58,7 @@ MT Metadata is a project led by [IRIS-PASSCAL MT Software working group](https:/
58
58
 
59
59
  Most people will be using the transfer functions, but a lot of that metadata comes from the time series metadata. This module supports both and has tried to make them more or less seamless to reduce complication.
60
60
 
61
- * **Version**: 0.3.6
61
+ * **Version**: 0.3.8
62
62
  * **Free software**: MIT license
63
63
  * **Documentation**: https://mt-metadata.readthedocs.io.
64
64
  * **Examples**: Click the `Binder` badge above and Jupyter Notebook examples are in **mt_metadata/examples/notebooks** and **docs/source/notebooks**
@@ -377,3 +377,60 @@ History
377
377
  -----------------------
378
378
 
379
379
  * update pandas.append to concat
380
+
381
+ 0.3.4 ()
382
+ -----------------------
383
+
384
+ * Update HISTORY.rst by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/179
385
+ * Remove filter direction attributes by @kkappler in https://github.com/kujaku11/mt_metadata/pull/181
386
+ * Fix issue 173 by @kkappler in https://github.com/kujaku11/mt_metadata/pull/182
387
+ * Patch 173 by @kkappler in https://github.com/kujaku11/mt_metadata/pull/183
388
+ * Add some common helper functions by @kkappler in https://github.com/kujaku11/mt_metadata/pull/185
389
+ * Bug fix in FC layer by @kkappler in https://github.com/kujaku11/mt_metadata/pull/186
390
+ * Fix mth5 issue 187 by @kkappler in https://github.com/kujaku11/mt_metadata/pull/187
391
+ * bug fixes by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/180
392
+ * updating how a TF is initiated, initialize xarray is expensive by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/188
393
+ * Change default value of `get_elevation` to False by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/191
394
+ * Updating git clone address in the readme by @xandrd in https://github.com/kujaku11/mt_metadata/pull/189
395
+ * Fix how Z-files read/write by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/192
396
+ * Adjust how TF._initialize_transfer_function is setup by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/193
397
+ * Add Ability to store processing configuration in TF by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/196
398
+ * Bump version: 0.3.3 → 0.3.4 by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/198
399
+
400
+
401
+ 0.3.5 ()
402
+ ---------------------
403
+
404
+ * Patches by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/200
405
+ * Fix issue #202 by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/203
406
+ * Patches by @kkappler in https://github.com/kujaku11/mt_metadata/pull/205
407
+ * Bump version: 0.3.4 → 0.3.5 by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/206
408
+
409
+ 0.3.6 ()
410
+ ---------------------
411
+
412
+ * add method for accessing mag channel names by @kkappler in https://github.com/kujaku11/mt_metadata/pull/210
413
+ * Patches by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/208
414
+ * Fix mth5 issue 207 by @kkappler in https://github.com/kujaku11/mt_metadata/pull/209
415
+ * Minor changes by @kkappler in https://github.com/kujaku11/mt_metadata/pull/211
416
+ * Patches by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/212
417
+
418
+ 0.3.7 (2024-08-16)
419
+ ---------------------
420
+
421
+ * Minor fixes numpy 2.0 by @kkappler in https://github.com/kujaku11/mt_metadata/pull/213
422
+ * Fix issue 216 by @kkappler in https://github.com/kujaku11/mt_metadata/pull/218
423
+ * Patches by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/219
424
+ * add u0 and r0 as regression parameters by @kkappler in https://github.com/kujaku11/mt_metadata/pull/220
425
+ * Updating EMTF XML and StationXML writers by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/217
426
+ * Patches by @kkappler in https://github.com/kujaku11/mt_metadata/pull/221
427
+ * Patches by @kkappler in https://github.com/kujaku11/mt_metadata/pull/223
428
+ * Bump version: 0.3.6 → 0.3.7 by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/225
429
+
430
+ 0.3.8 (2024-09-30)
431
+ ----------------------
432
+
433
+ * Add pop to ListDict by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/226
434
+ * Fix EDI Tipper flip by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/228
435
+ * Patches by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/227
436
+ * Bump version: 0.3.7 → 0.3.8 by @kujaku11 in https://github.com/kujaku11/mt_metadata/pull/229