Usage Examples

Here basic usage of the mt_metadata module are demonstrated.

MetadataBase Class

mt_metadata.base.MetadataBase is the base class for which all metadata objects are built upon. MetadataBase is built on Pydantic v2 and provides convenient methods to input and output metadata in different formats: XML, JSON, Python dictionary, and Pandas Series. It also provides functions to help the user understand what’s inside and ensures data validation.

Working with MetadataBase Objects

Let’s start with a practical example using the Location metadata class, which inherits from MetadataBase.

[4]:
from mt_metadata.common import Location

location = Location()

Methods of MetadataBase

MetadataBase (built on Pydantic v2) includes methods for converting to/from various formats:

  • ``to_dict()`` / ``from_dict()`` - Python dictionary

  • ``to_json()`` / ``from_json()`` - JSON string or file

  • ``to_xml()`` / ``from_xml()`` - XML elements

  • ``to_series()`` / ``from_series()`` - Pandas Series

It also provides:

  • ``get_all_fields()`` - Get all field definitions with metadata

  • ``get_attribute_list()`` - List of all available attributes

  • ``model_dump()`` - Pydantic method to export as dictionary

  • ``model_dump_json()`` - Pydantic method to export as JSON

[6]:
# Display key methods available on MetadataBase objects
key_methods = [
    "to_dict",
    "from_dict",
    "to_json",
    "from_json",
    "to_xml",
    "from_xml",
    "to_series",
    "from_series",
    "get_all_fields",
    "get_attribute_list",
    "model_dump",
    "model_dump_json",
]

available_methods = []
for method_name in key_methods:
    if hasattr(location, method_name):
        try:
            attr = getattr(location, method_name)
            if callable(attr):
                available_methods.append(method_name)
        except AttributeError:
            pass

print("Key Methods:")
for method in available_methods:
    print(f"\t{method}")
Key Methods:
        to_dict
        from_dict
        to_json
        from_json
        to_xml
        from_xml
        to_series
        from_series
        get_all_fields
        get_attribute_list
        model_dump
        model_dump_json
[7]:
# View available attributes
location.get_attribute_list()
[7]:
['datum',
 'elevation',
 'elevation_uncertainty',
 'latitude',
 'latitude_uncertainty',
 'longitude',
 'longitude_uncertainty',
 'x',
 'x2',
 'x_uncertainty',
 'y',
 'y2',
 'y_uncertainty',
 'z',
 'z2',
 'z_uncertainty']

Setting Values

You can set values directly using attribute access. Pydantic automatically validates the data types and values.

[8]:
location.latitude = 40.0
location.longitude = -120.0
location.elevation = 1500.0
location
[8]:
{
    "location": {
        "datum": "WGS 84",
        "elevation": 1500.0,
        "elevation_uncertainty": 0.0,
        "latitude": 40.0,
        "latitude_uncertainty": 0.0,
        "longitude": -120.0,
        "longitude_uncertainty": 0.0,
        "x": 0.0,
        "x2": 0.0,
        "x_uncertainty": 0.0,
        "y": 0.0,
        "y2": 0.0,
        "y_uncertainty": 0.0,
        "z": 0.0,
        "z2": 0.0,
        "z_uncertainty": 0.0
    }
}

The __str__ Representation

The string representation provides a readable summary.

[9]:
print(location)
{'latitude': 40.0, 'longitude': -120.0, 'elevation': 1500.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, '_class_name': 'location'}

Field Information

You can get detailed information about all fields including their types, descriptions, and constraints.

[10]:
# Get all fields with their metadata
fields = location.get_all_fields()
print(f"Total fields: {len(fields)}")
print(f"First few fields: {list(fields.keys())[:5]}")
Total fields: 16
First few fields: ['latitude', 'longitude', 'elevation', 'datum', 'x']
[ ]:
# Examine a specific field's metadata
latitude_field = fields["latitude"]
print(f"Type: {latitude_field['type']}")
print(f"Description: {latitude_field['description']}")
print(f"Required: {latitude_field['required']}")
temperature:
        alias: ['temp']
        default: None
        description: local temperature
        example: ambient
        options: ['ambient', 'air']
        required: False
        style: controlled vocabulary
        type: <class 'str'>
        units: celsius
==================================================

Validation with Pydantic

Validation is automatically handled by Pydantic v2. The validation process:

  1. Type checking: Ensures values match the prescribed type (int, float, str, etc.)

  2. Type coercion: Automatically converts compatible types when possible (e.g., “10.5” → 10.5)

  3. Constraints: Validates against constraints like min/max values, patterns, etc.

  4. Required fields: Ensures required fields are provided

  5. Nested validation: Validates nested MetadataBase objects recursively

[11]:
# Type coercion example - string to float
location.latitude = "45.5"
print(f"Latitude type: {type(location.latitude)}, value: {location.latitude}")
Latitude type: <class 'float'>, value: 45.5
[12]:
# Validation with constraints
location.latitude = (
    90.5  # Will raise validation error (latitude must be between -90 and 90)
)
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[12], line 2
      1 # Validation with constraints
----> 2 location.latitude = (
      3     90.5  # Will raise validation error (latitude must be between -90 and 90)
      4 )

File c:\Users\peaco\miniconda3\envs\py311\Lib\site-packages\pydantic\main.py:922, in BaseModel.__setattr__(self, name, value)
    920     self.__dict__[name] = value
    921 elif self.model_config.get('validate_assignment', None):
--> 922     self.__pydantic_validator__.validate_assignment(self, name, value)
    923 elif self.model_config.get('extra') != 'allow' and name not in self.__pydantic_fields__:
    924     # TODO - matching error
    925     raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"')

ValidationError: 1 validation error for Location
latitude
  Value error, latitude must be between -90 and 90 degrees [type=value_error, input_value=90.5, input_type=float]
    For further information visit https://errors.pydantic.dev/2.10/v/value_error

Working with Location Metadata

Let’s create a more complete example with nested metadata objects.

[15]:
from mt_metadata.common import StationLocation
here = StationLocation()
here.latitude = 40.7128
here.longitude = -74.0060
here.elevation = 10.0
here.declination.value = -12.5
here.declination.model = "WMM"
[16]:
print(here)
{'latitude': 40.7128, 'longitude': -74.006, 'elevation': 10.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, 'declination': {'comments': {'author': None, 'time_stamp': {'time_stamp': '1980-01-01T00:00:00+00:00', 'gps_time': False}, 'value': None, '_class_name': 'comment'}, 'model': 'WMM', 'epoch': None, 'value': -12.5, '_class_name': 'declination'}, 'geographic_location': {'country': None, 'state': None, 'county': None, 'township': None, 'section': None, 'quarter': None, 'parcel': None, '_class_name': 'geographic_location'}, '_class_name': 'station_location'}

Accessing Nested Attributes

You can access nested attributes directly using dot notation.

[ ]:
# Access nested declination object
print(f"Declination value: {here.declination.value}")
print(f"Declination model: {here.declination.model}")
location:
        declination.model = WMM
        declination.value = 10.0
        elevation = 0.0
        latitude = 0.0
        longitude = 0.0

Dictionary

The basic element that the metadata can be in is a Python dictionary with key, value pairs.

[17]:
# Export to dictionary (compact dotted notation)
here.to_dict()
[17]:
{'station_location': OrderedDict([('datum', 'WGS 84'),
              ('declination.comments.author', None),
              ('declination.comments.time_stamp', '1980-01-01T00:00:00+00:00'),
              ('declination.comments.value', None),
              ('declination.epoch', None),
              ('declination.model', 'WMM'),
              ('declination.value', -12.5),
              ('elevation', 10.0),
              ('elevation_uncertainty', 0.0),
              ('geographic_location.country', None),
              ('geographic_location.county', None),
              ('geographic_location.parcel', None),
              ('geographic_location.quarter', None),
              ('geographic_location.section', None),
              ('geographic_location.state', None),
              ('geographic_location.township', None),
              ('latitude', 40.7128),
              ('latitude_uncertainty', 0.0),
              ('longitude', -74.006),
              ('longitude_uncertainty', 0.0),
              ('x', 0.0),
              ('x2', 0.0),
              ('x_uncertainty', 0.0),
              ('y', 0.0),
              ('y2', 0.0),
              ('y_uncertainty', 0.0),
              ('z', 0.0),
              ('z2', 0.0),
              ('z_uncertainty', 0.0)])}
[18]:
# Import from dictionary with dotted notation
here.from_dict(
    {
        "location": {
            "declination.value": -11.0,
            "elevation": 759.0,
            "latitude": -34.0,
            "longitude": -104.0,
        }
    }
)
print(here)
{'latitude': -34.0, 'longitude': -104.0, 'elevation': 759.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, 'declination': {'comments': {'author': None, 'time_stamp': {'time_stamp': '1980-01-01T00:00:00+00:00', 'gps_time': False}, 'value': None, '_class_name': 'comment'}, 'model': 'WMM', 'epoch': None, 'value': -11.0, '_class_name': 'declination'}, 'geographic_location': {'country': None, 'state': None, 'county': None, 'township': None, 'section': None, 'quarter': None, 'parcel': None, '_class_name': 'geographic_location'}, '_class_name': 'station_location'}

JSON

JSON is a standard format human/machine readable and well supported in Python. There are methods to to read/write JSON files.

[19]:
# Compact JSON form
print(here.to_json())
{
    "station_location": {
        "datum": "WGS 84",
        "declination.comments.author": null,
        "declination.comments.time_stamp": "1980-01-01T00:00:00+00:00",
        "declination.comments.value": null,
        "declination.epoch": null,
        "declination.model": "WMM",
        "declination.value": -11.0,
        "elevation": 759.0,
        "elevation_uncertainty": 0.0,
        "geographic_location.country": null,
        "geographic_location.county": null,
        "geographic_location.parcel": null,
        "geographic_location.quarter": null,
        "geographic_location.section": null,
        "geographic_location.state": null,
        "geographic_location.township": null,
        "latitude": -34.0,
        "latitude_uncertainty": 0.0,
        "longitude": -104.0,
        "longitude_uncertainty": 0.0,
        "x": 0.0,
        "x2": 0.0,
        "x_uncertainty": 0.0,
        "y": 0.0,
        "y2": 0.0,
        "y_uncertainty": 0.0,
        "z": 0.0,
        "z2": 0.0,
        "z_uncertainty": 0.0
    }
}
[20]:
here.from_json(
    '{"location": {"declination.model": "WMM", "declination.value": 10.0, "elevation": 99.0, "latitude": 40.0, "longitude": -120.0}}'
)
print(here)
{'latitude': 40.0, 'longitude': -120.0, 'elevation': 99.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, 'declination': {'comments': {'author': None, 'time_stamp': {'time_stamp': '1980-01-01T00:00:00+00:00', 'gps_time': False}, 'value': None, '_class_name': 'comment'}, 'model': 'WMM', 'epoch': None, 'value': 10.0, '_class_name': 'declination'}, 'geographic_location': {'country': None, 'state': None, 'county': None, 'township': None, 'section': None, 'quarter': None, 'parcel': None, '_class_name': 'geographic_location'}, '_class_name': 'station_location'}
[21]:
# Nested JSON form
print(here.to_json(nested=True, indent=2))
{
  "station_location": {
    "datum": "WGS 84",
    "declination": {
      "comments": {
        "author": null,
        "time_stamp": "1980-01-01T00:00:00+00:00",
        "value": null
      },
      "epoch": null,
      "model": "WMM",
      "value": 10.0
    },
    "elevation": 99.0,
    "elevation_uncertainty": 0.0,
    "geographic_location": {
      "country": null,
      "county": null,
      "parcel": null,
      "quarter": null,
      "section": null,
      "state": null,
      "township": null
    },
    "latitude": 40.0,
    "latitude_uncertainty": 0.0,
    "longitude": -120.0,
    "longitude_uncertainty": 0.0,
    "x": 0.0,
    "x2": 0.0,
    "x_uncertainty": 0.0,
    "y": 0.0,
    "y2": 0.0,
    "y_uncertainty": 0.0,
    "z": 0.0,
    "z2": 0.0,
    "z_uncertainty": 0.0
  }
}
[22]:
here.from_json(
    '{"location": {"declination": {"model": "WMM", "value": -12.0}, "elevation": 199.0, "latitude": 20.0, "longitude": -110.0}}'
)
print(here)
{'latitude': 20.0, 'longitude': -110.0, 'elevation': 199.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, 'declination': {'comments': {'author': None, 'time_stamp': {'time_stamp': '1980-01-01T00:00:00+00:00', 'gps_time': False}, 'value': None, '_class_name': 'comment'}, 'model': 'WMM', 'epoch': None, 'value': -12.0, '_class_name': 'declination'}, 'geographic_location': {'country': None, 'state': None, 'county': None, 'township': None, 'section': None, 'quarter': None, 'parcel': None, '_class_name': 'geographic_location'}, '_class_name': 'station_location'}

XML

XML is also a common format for metadata, though not as human readable.

[23]:
print(here.to_xml(string=True))
<?xml version="1.0" encoding="UTF-8"?>
<station_location>
    <datum>WGS 84</datum>
    <declination>
        <comments>
            <author/>
            <time_stamp>1980-01-01T00:00:00+00:00</time_stamp>
            <value/>
        </comments>
        <epoch/>
        <model>WMM</model>
        <value>-12.0</value>
    </declination>
    <elevation>199.0</elevation>
    <elevation_uncertainty>0.0</elevation_uncertainty>
    <geographic_location>
        <country/>
        <county/>
        <parcel/>
        <quarter/>
        <section/>
        <state/>
        <township/>
    </geographic_location>
    <latitude>20.0</latitude>
    <latitude_uncertainty>0.0</latitude_uncertainty>
    <longitude>-110.0</longitude>
    <longitude_uncertainty>0.0</longitude_uncertainty>
    <x>0.0</x>
    <x2>0.0</x2>
    <x_uncertainty>0.0</x_uncertainty>
    <y>0.0</y>
    <y2>0.0</y2>
    <y_uncertainty>0.0</y_uncertainty>
    <z>0.0</z>
    <z2>0.0</z2>
    <z_uncertainty>0.0</z_uncertainty>
</station_location>

[24]:
from xml.etree import cElementTree as et

location = et.Element("location")
lat = et.SubElement(location, "latitude")
lat.text = "-10"
here.from_xml(location)
print(here)
{'latitude': -10.0, 'longitude': -110.0, 'elevation': 199.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, 'declination': {'comments': {'author': None, 'time_stamp': {'time_stamp': '1980-01-01T00:00:00+00:00', 'gps_time': False}, 'value': None, '_class_name': 'comment'}, 'model': 'WMM', 'epoch': None, 'value': -12.0, '_class_name': 'declination'}, 'geographic_location': {'country': None, 'state': None, 'county': None, 'township': None, 'section': None, 'quarter': None, 'parcel': None, '_class_name': 'geographic_location'}, '_class_name': 'station_location'}

Pandas Series

Pandas is a common data base object that is commonly used for columnar data. A series is basically like a single row in a data base.

[25]:
pd_series = here.to_series()
print(pd_series)
datum                                                 WGS 84
declination.comments.author                             None
declination.comments.time_stamp    1980-01-01T00:00:00+00:00
declination.comments.value                              None
declination.epoch                                       None
declination.model                                        WMM
declination.value                                      -12.0
elevation                                              199.0
elevation_uncertainty                                    0.0
geographic_location.country                             None
geographic_location.county                              None
geographic_location.parcel                              None
geographic_location.quarter                             None
geographic_location.section                             None
geographic_location.state                               None
geographic_location.township                            None
latitude                                               -10.0
latitude_uncertainty                                     0.0
longitude                                             -110.0
longitude_uncertainty                                    0.0
x                                                        0.0
x2                                                       0.0
x_uncertainty                                            0.0
y                                                        0.0
y2                                                       0.0
y_uncertainty                                            0.0
z                                                        0.0
z2                                                       0.0
z_uncertainty                                            0.0
dtype: object
[26]:
from pandas import Series

location_series = Series(
    {
        "declination.model": "WMM",
        "declination.value": -14.0,
        "elevation": 399.0,
        "latitude": -14.0,
        "longitude": -112.0,
    }
)

here.from_series(location_series)
print(here)
{'latitude': -14.0, 'longitude': -112.0, 'elevation': 399.0, 'datum': 'WGS 84', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'latitude_uncertainty': 0.0, 'longitude_uncertainty': 0.0, 'elevation_uncertainty': 0.0, 'x2': 0.0, 'y2': 0.0, 'z2': 0.0, 'x_uncertainty': 0.0, 'y_uncertainty': 0.0, 'z_uncertainty': 0.0, 'declination': {'comments': {'author': None, 'time_stamp': {'time_stamp': '1980-01-01T00:00:00+00:00', 'gps_time': False}, 'value': None, '_class_name': 'comment'}, 'model': 'WMM', 'epoch': None, 'value': -14.0, '_class_name': 'declination'}, 'geographic_location': {'country': None, 'state': None, 'county': None, 'township': None, 'section': None, 'quarter': None, 'parcel': None, '_class_name': 'geographic_location'}, '_class_name': 'station_location'}
[ ]: