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:
Type checking: Ensures values match the prescribed type (int, float, str, etc.)
Type coercion: Automatically converts compatible types when possible (e.g., “10.5” → 10.5)
Constraints: Validates against constraints like min/max values, patterns, etc.
Required fields: Ensures required fields are provided
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'}
[ ]: