Skip to content

Commit

Permalink
libdiagtable: Add more doxygen comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesse Lentz committed Dec 13, 2024
1 parent e62168b commit f8156d8
Showing 1 changed file with 35 additions and 5 deletions.
40 changes: 35 additions & 5 deletions fms_yaml_tools/diag_table/libdiagtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@


def strip_none(d):
"""Strip all None values out of a dictionary and return the result"""
return dict(kv for kv in d.items() if kv[1] is not None)


Expand All @@ -37,14 +38,15 @@ def dict_assert_mergeable(a, b):


def parse_negate_flag(s):
"""Check if the first character of a filter string is '~', which indicates that the filter shall be negated."""
if s[0] == "~":
return (s[1:], True)
else:
return (s, False)


def file_filter_factory(filter_spec):
"""Return a function to be used as a file filter"""
"""Return a function to be used as a file filter, from a specification string or a list thereof"""
if callable(filter_spec):
return filter_spec

Expand Down Expand Up @@ -73,7 +75,7 @@ def file_filter(file_obj):


def var_filter_factory(filter_spec):
"""Return a function to be used as a variable filter"""
"""Return a function to be used as a variable filter, from a specification string or a list thereof"""
if callable(filter_spec):
return filter_spec

Expand Down Expand Up @@ -122,9 +124,8 @@ def var_filter(file_obj, var_obj):


class DiagTableBase:
"""This class should not be used directly. Child classes must
implement a static `validate_field` method, which decides whether or
not a key/value pair is valid."""
"""This class should not be used directly. Child classes must implement a static `validate_field` method, which
decides whether or not a key/value pair is valid."""

def __add__(a, b):
a = copy.deepcopy(a)
Expand All @@ -137,15 +138,19 @@ def __or__(a, b):
return a

def validate(self):
"""Validate every field of the object"""
for key, value in self.__dict__.items():
if value is not None:
self.__class__.validate_field(key, value)

def set(self, key, value):
"""Generic setter with validation"""
self.__class__.validate_field(key, value)
self.__dict__[key] = value

def coerce_type(self, other):
"""Check that the `other` operand in a binary operation is either of the same type, or is a dictionary from
which an object of the correct type can be created"""
if type(other) is dict:
other = self.__class__(other)

Expand All @@ -156,6 +161,7 @@ def coerce_type(self, other):
return other

def dump_yaml(self, abstract=None, fh=None):
"""Return the object as a YAML string"""
return yaml.safe_dump(self.render(abstract), fh, default_flow_style=False, sort_keys=False)

@classmethod
Expand All @@ -177,6 +183,7 @@ class DiagTable(DiagTableBase):

@staticmethod
def validate_field(key, value):
"""Decide whether or not a given key/value pair is valid"""
if key == "title":
assert type(value) is str
elif key == "base_date":
Expand Down Expand Up @@ -210,6 +217,7 @@ def __init__(self, diag_table={}):
self.validate()

def render(self, abstract=None):
"""Return a dictionary representation of the object"""
table = strip_none(self.__dict__)
table["diag_files"] = list(f.render(abstract) for f in table["diag_files"])

Expand All @@ -219,24 +227,28 @@ def render(self, abstract=None):
return table

def filter_files(self, filter):
"""Apply a file filter and return the resulting DiagTable object"""
filter = file_filter_factory(filter)

table = copy.copy(self)
table.diag_files = self.get_filtered_files(filter)
return table

def filter_vars(self, filter):
"""Apply a variable filter and return the resulting DiagTable object"""
filter = var_filter_factory(filter)

table = copy.copy(self)
table.diag_files = [f.filter_vars(filter) for f in table.diag_files]
return table

def get_filtered_files(self, filter):
"""Apply a file filter and return the resulting list of DiagTableFile objects"""
filter = file_filter_factory(filter)
return [f for f in self.diag_files if filter(f)]

def get_filtered_vars(self, filter):
"""Apply a variable filter and return the resulting list of DiagTableVar objects"""
filter = var_filter_factory(filter)
filtered_vars = []

Expand Down Expand Up @@ -324,6 +336,7 @@ class DiagTableFile(DiagTableBase):

@staticmethod
def validate_field(key, value):
"""Decide whether or not a given key/value pair is valid"""
if key == "file_name":
assert type(value) is str
elif key == "freq":
Expand Down Expand Up @@ -380,6 +393,8 @@ def __init__(self, file={}):
self.validate()

def __iadd__(self, other):
"""Symmetric merge of two DiagTableFile objects. Any conflict between the
two operands shall result in a failure."""
other = copy.copy(self.coerce_type(other))

if self.global_meta and other.global_meta:
Expand Down Expand Up @@ -407,6 +422,8 @@ def __iadd__(self, other):
return self

def __ior__(self, other):
"""Asymmetric merge of two DiagTableFile objects. Any conflict between the
two operands will resolve to the `other` value."""
other = copy.copy(self.coerce_type(other))

if self.global_meta and other.global_meta:
Expand All @@ -432,6 +449,7 @@ def __ior__(self, other):
return self

def render(self, abstract=None):
"""Return a dictionary representation of the object"""
file = strip_none(self.__dict__)
file["varlist"] = list(v.render(abstract) for v in file["varlist"])

Expand Down Expand Up @@ -518,6 +536,7 @@ class DiagTableVar(DiagTableBase):

@staticmethod
def validate_field(key, value):
"""Decide whether or not a given key/value pair is valid"""
if key == "var_name":
assert type(value) is str
elif key == "kind":
Expand Down Expand Up @@ -555,6 +574,8 @@ def __init__(self, var={}):
self.validate()

def __iadd__(self, other):
"""Symmetric merge of two DiagTableVar objects. Any conflict between the
two operands shall result in a failure."""
other = self.coerce_type(other)

if "attributes" in self.__dict__ and "attributes" in other.__dict__:
Expand All @@ -568,6 +589,8 @@ def __iadd__(self, other):
return self

def __ior__(self, other):
"""Asymmetric merge of two DiagTableVar objects. Any conflict between the
two operands will resolve to the `other` value."""
other = self.coerce_type(other)

if "attributes" in self.__dict__ and "attributes" in other.__dict__:
Expand All @@ -579,6 +602,7 @@ def __ior__(self, other):
return self

def render(self, abstract=None):
"""Return a dictionary representation of the object"""
if abstract and "var" in abstract:
return self.var_name
else:
Expand Down Expand Up @@ -627,6 +651,7 @@ class DiagTableSubRegion(DiagTableBase):

@staticmethod
def validate_field(key, value):
"""Decide whether or not a given key/value pair is valid"""
if key == "grid_type":
assert value in ("indices", "latlon")
elif key in ("corner1", "corner2", "corner3", "corner4"):
Expand All @@ -649,17 +674,22 @@ def __init__(self, sub_region={}):
self.validate()

def __iadd__(self, other):
"""Symmetric merge of two DiagTableSubRegion objects. Any conflict between the
two operands shall result in a failure."""
other = self.coerce_type(other)
dict_assert_mergeable(self.__dict__, other.__dict__)
self.__dict__ |= other.__dict__
return self

def __ior__(self, other):
"""Asymmetric merge of two DiagTableSubRegion objects. Any conflict between the
two operands will resolve to the `other` value."""
other = self.coerce_type(other)
self.__dict__ |= other.__dict__
return self

def render(self):
"""Return a dictionary representation of the object"""
return strip_none(self.__dict__)

def set_grid_type(self, grid_type):
Expand Down

0 comments on commit f8156d8

Please sign in to comment.