From 39c2cd31c8fb8a4222105e3b79158dbcb482847b Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Tue, 14 Jul 2026 13:14:20 -0700 Subject: [PATCH 1/2] feat: add function to create HTML representations of custom classes --- .../notebooks/GRACE-Geostrophic-Maps.ipynb | 6 ++- .../notebooks/GRACE-Harmonic-Plots.ipynb | 14 +++---- .../notebooks/GRACE-Spatial-Error.ipynb | 6 ++- doc/source/notebooks/GRACE-Spatial-Maps.ipynb | 15 +++---- gravity_toolkit/geocenter.py | 19 ++++++++- gravity_toolkit/harmonics.py | 22 ++++++++++- gravity_toolkit/read_GIA_model.py | 33 +++++++++++++++- gravity_toolkit/read_love_numbers.py | 19 ++++++++- gravity_toolkit/spatial.py | 23 ++++++++++- gravity_toolkit/tools.py | 11 +++--- gravity_toolkit/units.py | 23 ++++++++++- gravity_toolkit/utilities.py | 39 +++++++++++++++++++ 12 files changed, 195 insertions(+), 35 deletions(-) diff --git a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb index 0221b80..34c2ab5 100644 --- a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb +++ b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb @@ -80,7 +80,7 @@ "# if updating the local data\n", "if widgets.update.value:\n", " # run podaac sync program to get latest data\n", - " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint\n", + " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint.value\n", " # run GRACE date program to verify months\n", " !run_grace_date.py --directory=$widgets.base_directory --verbose\n", " # get geocenter data from Sutterley and Velicogna (2019)\n", @@ -233,7 +233,9 @@ "# number of time steps\n", "nt = len(months)\n", "# flag for spherical harmonic order\n", - "order_str = f'M{MMAX:d}' if (MMAX != LMAX) else ''" + "order_str = f'M{MMAX:d}' if (MMAX != LMAX) else ''\n", + "# inspect GRACE harmonics\n", + "GRACE_Ylms" ] }, { diff --git a/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb b/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb index 6f83b71..7f03b74 100644 --- a/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb +++ b/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb @@ -78,7 +78,7 @@ "# if updating the local data\n", "if widgets.update.value:\n", " # run podaac sync program to get latest data\n", - " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint\n", + " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint.value\n", " # run GRACE date program to verify months\n", " !run_grace_date.py --directory=$widgets.base_directory --verbose\n", " # get geocenter data from Sutterley and Velicogna (2019)\n", @@ -228,7 +228,9 @@ "GRACE_Ylms = gravtk.harmonics().from_dict(Ylms)\n", "GRACE_Ylms.mean(apply=True)\n", "# number of time steps\n", - "nt = len(months)" + "nt = len(months)\n", + "# inspect GRACE harmonics\n", + "GRACE_Ylms" ] }, { @@ -552,11 +554,9 @@ } ], "metadata": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - }, "kernelspec": { - "display_name": "Python 3.8.10 64-bit", + "display_name": "py13", + "language": "python", "name": "python3" }, "language_info": { @@ -569,7 +569,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.13.0" } }, "nbformat": 4, diff --git a/doc/source/notebooks/GRACE-Spatial-Error.ipynb b/doc/source/notebooks/GRACE-Spatial-Error.ipynb index b66aafe..3177750 100644 --- a/doc/source/notebooks/GRACE-Spatial-Error.ipynb +++ b/doc/source/notebooks/GRACE-Spatial-Error.ipynb @@ -75,7 +75,7 @@ "# if updating the local data\n", "if widgets.update.value:\n", " # run podaac sync program to get latest data\n", - " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint\n", + " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint.value\n", " # run GRACE date program to verify months\n", " !run_grace_date.py --directory=$widgets.base_directory --verbose\n", " # get geocenter data from Sutterley and Velicogna (2019)\n", @@ -225,7 +225,9 @@ "GRACE_Ylms = gravtk.harmonics().from_dict(Ylms)\n", "GRACE_Ylms.mean(apply=True)\n", "# number of time steps\n", - "nt = len(months)" + "nt = len(months)\n", + "# inspect GRACE harmonics\n", + "GRACE_Ylms" ] }, { diff --git a/doc/source/notebooks/GRACE-Spatial-Maps.ipynb b/doc/source/notebooks/GRACE-Spatial-Maps.ipynb index a4361ae..99538b2 100644 --- a/doc/source/notebooks/GRACE-Spatial-Maps.ipynb +++ b/doc/source/notebooks/GRACE-Spatial-Maps.ipynb @@ -90,7 +90,7 @@ "# if updating the local data\n", "if widgets.update.value:\n", " # run podaac sync program to get latest data\n", - " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint\n", + " !podaac_cumulus.py --directory=$widgets.base_directory --endpoint=$widgets.endpoint.value\n", " # run GRACE date program to verify months\n", " !run_grace_date.py --directory=$widgets.base_directory --verbose\n", " # get geocenter data from Sutterley and Velicogna (2019)\n", @@ -270,7 +270,9 @@ "# number of time steps\n", "nt = len(months)\n", "# flag for spherical harmonic order\n", - "order_str = f'M{MMAX:d}' if (MMAX != LMAX) else ''" + "order_str = f'M{MMAX:d}' if (MMAX != LMAX) else ''\n", + "# inspect GRACE harmonics\n", + "GRACE_Ylms" ] }, { @@ -990,7 +992,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.10 64-bit", + "display_name": "py13", "language": "python", "name": "python3" }, @@ -1004,12 +1006,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.9" - }, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/gravity_toolkit/geocenter.py b/gravity_toolkit/geocenter.py index a6c932b..edcf75d 100644 --- a/gravity_toolkit/geocenter.py +++ b/gravity_toolkit/geocenter.py @@ -16,6 +16,7 @@ UPDATE HISTORY: Updated 07/2026: add dunder (magic) methods for mathematical operations + add HTML representation of geocenter class Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment Updated 09/2023: add group option to netCDF read function @@ -54,7 +55,7 @@ import pathlib import numpy as np import gravity_toolkit.time -from gravity_toolkit.utilities import import_dependency +from gravity_toolkit.utilities import import_dependency, html_repr # attempt imports netCDF4 = import_dependency('netCDF4') @@ -1289,11 +1290,25 @@ def fields(self): def __str__(self): """String representation of the ``geocenter`` object""" properties = ['gravity_toolkit.geocenter'] - if self.month: + if any(self.month): properties.append(f' start_month: {min(self.month)}') properties.append(f' end_month: {max(self.month)}') return '\n'.join(properties) + def __repr__(self): + """Representation of the ``geocenter`` object""" + return self.__str__() + + def _repr_html_(self): + """HTML representation of the ``geocenter`` object""" + header = 'gravity_toolkit.geocenter' + properties = {} + if any(self.month): + properties['start_month'] = min(self.month) + properties['end_month'] = max(self.month) + properties['slices'] = self.__len__() + return html_repr(header, properties) + def __add__(self, other): """Add values to a ``geocenter`` object""" temp = self.copy() diff --git a/gravity_toolkit/harmonics.py b/gravity_toolkit/harmonics.py index b90d090..5f5f868 100644 --- a/gravity_toolkit/harmonics.py +++ b/gravity_toolkit/harmonics.py @@ -26,6 +26,7 @@ UPDATE HISTORY: Updated 07/2026: add dunder (magic) methods for mathematical operations + add HTML representation of spatial class Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment Updated 10/2023: place time and month variables in try/except block @@ -110,7 +111,7 @@ from gravity_toolkit.destripe_harmonics import destripe_harmonics from gravity_toolkit.read_gfc_harmonics import read_gfc_harmonics from gravity_toolkit.read_GRACE_harmonics import read_GRACE_harmonics -from gravity_toolkit.utilities import import_dependency, reify +from gravity_toolkit.utilities import import_dependency, reify, html_repr # attempt imports geoidtk = import_dependency('geoid_toolkit') @@ -1976,11 +1977,28 @@ def __str__(self): properties.append(f' max_degree: {self.lmax}') if self.mmax and (self.mmax != self.lmax): properties.append(f' max_order: {self.mmax}') - if self.month: + if any(self.month): properties.append(f' start_month: {min(self.month)}') properties.append(f' end_month: {max(self.month)}') return '\n'.join(properties) + def __repr__(self): + """Representation of the ``harmonics`` object""" + return self.__str__() + + def _repr_html_(self): + """HTML representation of the ``harmonics`` object""" + header = 'gravity_toolkit.harmonics' + properties = {} + properties['max_degree'] = self.lmax + if self.mmax and (self.mmax != self.lmax): + properties['max_order'] = self.mmax + if any(self.month): + properties['start_month'] = min(self.month) + properties['end_month'] = max(self.month) + properties['slices'] = self.__len__() + return html_repr(header, properties) + def __add__(self, other): """Add values to a ``harmonics`` object""" temp = self.copy() diff --git a/gravity_toolkit/read_GIA_model.py b/gravity_toolkit/read_GIA_model.py index 1ef260c..60e94eb 100755 --- a/gravity_toolkit/read_GIA_model.py +++ b/gravity_toolkit/read_GIA_model.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ read_GIA_model.py -Written by Tyler Sutterley (05/2023) +Written by Tyler Sutterley (07/2026) Reads GIA data files that can come in various formats depending on the group Outputs spherical harmonics for the GIA rates and the GIA model parameters @@ -96,6 +96,7 @@ https://doi.org/10.1002/2016JB013844 UPDATE HISTORY: + Updated 07/2026: add string and HTML representations of gia class Updated 05/2023: use pathlib to define and operate on paths Updated 03/2023: case insensitive searching for HDF5 and netCDF4 attributes convert shape and ndim to harmonic class properties @@ -148,6 +149,7 @@ import pathlib import numpy as np from gravity_toolkit.harmonics import harmonics +from gravity_toolkit.utilities import html_repr _known_gia_types = [ 'IJ05-R2', @@ -785,3 +787,32 @@ def from_GIA(self, filename, **kwargs): # assign degree and order fields self.update_dimensions() return self + + def __str__(self): + """String representation of the ``gia`` object""" + properties = ['gravity_toolkit.gia'] + properties.append(f' title: {self.title}') + properties.append(f' citation: {self.citation}') + properties.append(f' max_degree: {self.lmax}') + if self.mmax and (self.mmax != self.lmax): + properties.append(f' max_order: {self.mmax}') + properties.append(f' reference: {self.reference}') + properties.append(f' url: {self.url}') + return '\n'.join(properties) + + def __repr__(self): + """Representation of the ``gia`` object""" + return self.__str__() + + def _repr_html_(self): + """HTML representation of the ``gia`` object""" + header = 'gravity_toolkit.gia' + properties = {} + properties['title'] = self.title + properties['citation'] = self.citation + properties['max_degree'] = self.lmax + if self.mmax and (self.mmax != self.lmax): + properties['max_order'] = self.mmax + properties['reference'] = self.reference + properties['url'] = self.url + return html_repr(header, properties) diff --git a/gravity_toolkit/read_love_numbers.py b/gravity_toolkit/read_love_numbers.py index 47db2cc..998856f 100755 --- a/gravity_toolkit/read_love_numbers.py +++ b/gravity_toolkit/read_love_numbers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ read_love_numbers.py -Written by Tyler Sutterley (11/2024) +Written by Tyler Sutterley (07/2026) Reads sets of load Love numbers from PREM and applies isomorphic parameters Linearly interpolates load Love/Shida numbers for missing degrees @@ -56,6 +56,7 @@ 103(B12), 30205-30229, (1998) UPDATE HISTORY: + Updated 07/2026: add HTML representation of love_numbers class Updated 11/2024: allow reading where degree is infinite Updated 05/2024: make subscriptable and allow item assignment Updated 08/2023: add string representation of the love_numbers class @@ -94,7 +95,7 @@ import logging import pathlib import numpy as np -from gravity_toolkit.utilities import get_data_path +from gravity_toolkit.utilities import get_data_path, html_repr # default maximum degree and order in case of infinite _default_max_degree = 100000 @@ -582,6 +583,20 @@ def __str__(self): properties.append(f' reference: {self.reference}') return '\n'.join(properties) + def __repr__(self): + """Representation of the ``love_numbers`` object""" + return self.__str__() + + def _repr_html_(self): + """HTML representation of the ``love_numbers`` object""" + header = 'gravity_toolkit.love_numbers' + properties = {} + properties['citation'] = self.citation + properties['earth_model'] = self.model + properties['max_degree'] = self.lmax + properties['reference'] = self.reference + return html_repr(header, properties) + def __len__(self): """Number of degrees""" return len(self.l) diff --git a/gravity_toolkit/spatial.py b/gravity_toolkit/spatial.py index 5653973..7b65b9a 100644 --- a/gravity_toolkit/spatial.py +++ b/gravity_toolkit/spatial.py @@ -22,6 +22,7 @@ UPDATE HISTORY: Updated 07/2026: add dunder (magic) methods for mathematical operations add option to change the output format for ascii files + add HTML representation of spatial class Updated 10/2024: allow 2D and 3D arrays in output netCDF4 files Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment @@ -89,7 +90,7 @@ import numpy as np import gravity_toolkit.version from gravity_toolkit.time import adjust_months, calendar_to_grace -from gravity_toolkit.utilities import import_dependency +from gravity_toolkit.utilities import import_dependency, html_repr # attempt imports h5py = import_dependency('h5py') @@ -1822,11 +1823,29 @@ def __str__(self): properties.append(f' extent: {extent}') shape = ', '.join(map(str, self.shape)) properties.append(f' shape: {shape}') - if self.month: + if any(self.month): properties.append(f' start_month: {min(self.month)}') properties.append(f' end_month: {max(self.month)}') return '\n'.join(properties) + def __repr__(self): + """Representation of the ``spatial`` object""" + return self.__str__() + + def _repr_html_(self): + """HTML representation of the ``spatial`` object""" + header = 'gravity_toolkit.spatial' + properties = {} + extent = ', '.join(map(str, self.extent)) + properties['extent'] = f'[{extent}]' + shape = ', '.join(map(str, self.shape)) + properties['shape'] = f'({shape})' + if any(self.month): + properties['start_month'] = min(self.month) + properties['end_month'] = max(self.month) + properties['slices'] = self.__len__() + return html_repr(header, properties) + def __add__(self, other): """Add values to a ``spatial`` object""" temp = self.copy() diff --git a/gravity_toolkit/tools.py b/gravity_toolkit/tools.py index c31d511..8967d03 100644 --- a/gravity_toolkit/tools.py +++ b/gravity_toolkit/tools.py @@ -28,6 +28,7 @@ UPDATE HISTORY: Updated 07/2026: use np.radians and np.degrees for angle conversions + set the default path for widgets to the cache path Updated 11/2024: fix deprecated widget object copies Updated 04/2024: add widget for setting endpoint for accessing PODAAC data place colormap registration within try/except to check for existing @@ -52,7 +53,7 @@ import numpy as np import scipy.interpolate from gravity_toolkit.spatial import spatial -from gravity_toolkit.utilities import get_data_path +from gravity_toolkit.utilities import get_data_path, get_cache_path from gravity_toolkit.grace_find_months import grace_find_months # imports with warnings if not present @@ -83,7 +84,7 @@ class widgets: def __init__(self, **kwargs): """Widgets and functions for running GRACE/GRACE-FO analyses""" # set default keyword arguments - kwargs.setdefault('directory', pathlib.Path.cwd()) + kwargs.setdefault('directory', get_cache_path(ensure_exists=False)) kwargs.setdefault('defaults', ['CSR', 'RL06', 'GSM', 60]) kwargs.setdefault('style', {}) # set style @@ -131,7 +132,7 @@ def select_directory(self, **kwargs): # update local data with PO.DAAC https servers self.update = ipywidgets.Checkbox( - value=True, + value=False, description='Update data?', disabled=False, style=self.style, @@ -307,7 +308,7 @@ def select_options(self, **kwargs): max=self.defaults[3], value=self.defaults[3], step=1, - description='max:', + description=r'max:', disabled=False, style=self.style, ) @@ -318,7 +319,7 @@ def select_options(self, **kwargs): max=self.defaults[3], value=self.defaults[3], step=1, - description='mmax:', + description=r'mmax:', disabled=False, style=self.style, ) diff --git a/gravity_toolkit/units.py b/gravity_toolkit/units.py index 8732475..59c4e49 100644 --- a/gravity_toolkit/units.py +++ b/gravity_toolkit/units.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ units.py -Written by Tyler Sutterley (05/2024) +Written by Tyler Sutterley (07/2026) Contributions by Hugo Lecomte Class for converting GRACE/GRACE-FO Level-2 data to specific units @@ -10,6 +10,7 @@ numpy: Scientific Computing Tools For Python (https://numpy.org) UPDATE HISTORY: + Updated 07/2026: add string and HTML representations of units class Updated 05/2024: make subscriptable and allow item assignment Updated 09/2023: added property for the approximate mass of the Earth Updated 03/2023: include option to not compensate for elastic deformation @@ -30,6 +31,7 @@ from __future__ import annotations import numpy as np +from gravity_toolkit.utilities import html_repr class units(object): @@ -347,6 +349,25 @@ def get_attributes(var: str) -> str: except Exception as exc: raise ValueError(f'Unknown units {var}') from exc + def __str__(self): + """String representation of the ``units`` object""" + properties = ['gravity_toolkit.units'] + if self.lmax is not None: + properties.append(f' max_degree: {self.lmax}') + return '\n'.join(properties) + + def __repr__(self): + """Representation of the ``units`` object""" + return self.__str__() + + def _repr_html_(self): + """HTML representation of the ``units`` object""" + header = 'gravity_toolkit.units' + properties = {} + if self.lmax is not None: + properties['max_degree'] = str(self.lmax) + return html_repr(header, properties) + def __getitem__(self, key): return getattr(self, key) diff --git a/gravity_toolkit/utilities.py b/gravity_toolkit/utilities.py index da2abf2..a379a1e 100644 --- a/gravity_toolkit/utilities.py +++ b/gravity_toolkit/utilities.py @@ -13,6 +13,7 @@ UPDATE HISTORY: Updated 07/2026: can use an environment variable to set cache directory this overrides the default platform-specific cache directory + add function to create HTML representations of custom classes Updated 10/2025: switch from_gfz to https as ftp server is being retired Updated 11/2024: simplify unique file name function add function to scrape GSFC website for GRACE mascon urls @@ -268,6 +269,44 @@ def __get__(self, inst, objtype=None): return val +def html_repr( + header: str, + properties: dict, + pretty: bool = False, +) -> str: + """ + HTML representation for custom classes + + Parameters + ---------- + header: str + Name of the class + properties: dict + class properties to display + pretty: bool, default False + pretty print the HTML + """ + # HTML components + html_components = [] + # method of joining HTML components + joiner = '\n' if pretty else '' + # format representation as sample outputs + html_components.append("") + # add header + html_components.append("
") + html_components.append(str(header)) + html_components.append('
') + # create a list for class properties + if properties: + property_items = joiner.join( + f'
  • {k}: {v}
  • ' for k, v in properties.items() + ) + html_components.append(f'') + html_components.append('
    ') + # join components + return joiner.join(html_components) + + # PURPOSE: get the hash value of a file def get_hash(local: str | io.IOBase | pathlib.Path, algorithm: str = 'md5'): """ From cca6c5741a9e3c78703b74e48cd2ef8543e3d9cf Mon Sep 17 00:00:00 2001 From: Tyler Sutterley Date: Tue, 14 Jul 2026 13:56:49 -0700 Subject: [PATCH 2/2] fix: HTML escape html strings fix: case where there are months but it is an integer --- gravity_toolkit/geocenter.py | 16 ++++++++-------- gravity_toolkit/harmonics.py | 18 +++++++++--------- gravity_toolkit/spatial.py | 16 ++++++++-------- gravity_toolkit/utilities.py | 8 ++++++-- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/gravity_toolkit/geocenter.py b/gravity_toolkit/geocenter.py index edcf75d..1133614 100644 --- a/gravity_toolkit/geocenter.py +++ b/gravity_toolkit/geocenter.py @@ -1290,9 +1290,9 @@ def fields(self): def __str__(self): """String representation of the ``geocenter`` object""" properties = ['gravity_toolkit.geocenter'] - if any(self.month): - properties.append(f' start_month: {min(self.month)}') - properties.append(f' end_month: {max(self.month)}') + if np.any(self.month): + properties.append(f' start_month: {np.min(self.month)}') + properties.append(f' end_month: {np.max(self.month)}') return '\n'.join(properties) def __repr__(self): @@ -1303,10 +1303,10 @@ def _repr_html_(self): """HTML representation of the ``geocenter`` object""" header = 'gravity_toolkit.geocenter' properties = {} - if any(self.month): - properties['start_month'] = min(self.month) - properties['end_month'] = max(self.month) - properties['slices'] = self.__len__() + if np.any(self.month): + properties['start_month'] = np.min(self.month) + properties['end_month'] = np.max(self.month) + properties['slices'] = self.__len__() return html_repr(header, properties) def __add__(self, other): @@ -1376,7 +1376,7 @@ def __truediv__(self, other): def __len__(self): """Number of months""" - return len(self.month) if np.any(self.month) else 0 + return len(np.atleast_1d(self.month)) if np.any(self.month) else 0 def __iter__(self): """Iterate over GRACE/GRACE-FO months""" diff --git a/gravity_toolkit/harmonics.py b/gravity_toolkit/harmonics.py index 5f5f868..53617fd 100644 --- a/gravity_toolkit/harmonics.py +++ b/gravity_toolkit/harmonics.py @@ -26,7 +26,7 @@ UPDATE HISTORY: Updated 07/2026: add dunder (magic) methods for mathematical operations - add HTML representation of spatial class + add HTML representation of harmonics class Updated 06/2024: use wrapper to importlib for optional dependencies Updated 05/2024: make subscriptable and allow item assignment Updated 10/2023: place time and month variables in try/except block @@ -1977,9 +1977,9 @@ def __str__(self): properties.append(f' max_degree: {self.lmax}') if self.mmax and (self.mmax != self.lmax): properties.append(f' max_order: {self.mmax}') - if any(self.month): - properties.append(f' start_month: {min(self.month)}') - properties.append(f' end_month: {max(self.month)}') + if np.any(self.month): + properties.append(f' start_month: {np.min(self.month)}') + properties.append(f' end_month: {np.max(self.month)}') return '\n'.join(properties) def __repr__(self): @@ -1993,10 +1993,10 @@ def _repr_html_(self): properties['max_degree'] = self.lmax if self.mmax and (self.mmax != self.lmax): properties['max_order'] = self.mmax - if any(self.month): - properties['start_month'] = min(self.month) - properties['end_month'] = max(self.month) - properties['slices'] = self.__len__() + if np.any(self.month): + properties['start_month'] = np.min(self.month) + properties['end_month'] = np.max(self.month) + properties['slices'] = self.__len__() return html_repr(header, properties) def __add__(self, other): @@ -2066,7 +2066,7 @@ def __truediv__(self, other): def __len__(self): """Number of months""" - return len(self.month) if np.any(self.month) else 0 + return len(np.atleast_1d(self.month)) if np.any(self.month) else 0 def __iter__(self): """Iterate over GRACE/GRACE-FO months""" diff --git a/gravity_toolkit/spatial.py b/gravity_toolkit/spatial.py index 7b65b9a..1667622 100644 --- a/gravity_toolkit/spatial.py +++ b/gravity_toolkit/spatial.py @@ -1823,9 +1823,9 @@ def __str__(self): properties.append(f' extent: {extent}') shape = ', '.join(map(str, self.shape)) properties.append(f' shape: {shape}') - if any(self.month): - properties.append(f' start_month: {min(self.month)}') - properties.append(f' end_month: {max(self.month)}') + if np.any(self.month): + properties.append(f' start_month: {np.min(self.month)}') + properties.append(f' end_month: {np.max(self.month)}') return '\n'.join(properties) def __repr__(self): @@ -1840,10 +1840,10 @@ def _repr_html_(self): properties['extent'] = f'[{extent}]' shape = ', '.join(map(str, self.shape)) properties['shape'] = f'({shape})' - if any(self.month): - properties['start_month'] = min(self.month) - properties['end_month'] = max(self.month) - properties['slices'] = self.__len__() + if np.any(self.month): + properties['start_month'] = np.min(self.month) + properties['end_month'] = np.max(self.month) + properties['slices'] = self.__len__() return html_repr(header, properties) def __add__(self, other): @@ -1901,7 +1901,7 @@ def __truediv__(self, other): def __len__(self): """Number of months""" - return len(self.month) if np.any(self.month) else 0 + return len(np.atleast_1d(self.month)) if np.any(self.month) else 0 def __iter__(self): """Iterate over GRACE/GRACE-FO months""" diff --git a/gravity_toolkit/utilities.py b/gravity_toolkit/utilities.py index a379a1e..3b4ab02 100644 --- a/gravity_toolkit/utilities.py +++ b/gravity_toolkit/utilities.py @@ -90,6 +90,7 @@ import lxml.etree import subprocess import platformdirs +import html as _html import calendar, time if sys.version_info[0] == 2: @@ -290,16 +291,19 @@ class properties to display html_components = [] # method of joining HTML components joiner = '\n' if pretty else '' + # validate strings to be HTML safe + escape = lambda x: _html.escape(str(x), quote=True) # format representation as sample outputs html_components.append("") # add header html_components.append("
    ") - html_components.append(str(header)) + html_components.append(escape(header)) html_components.append('
    ') # create a list for class properties if properties: property_items = joiner.join( - f'
  • {k}: {v}
  • ' for k, v in properties.items() + f'
  • {escape(k)}: {escape(v)}
  • ' + for k, v in properties.items() ) html_components.append(f'
      {property_items}
    ') html_components.append('
    ')