From 71c7e011b11481c73c354b076e9bc6d9a622a9f7 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 10 Apr 2023 12:09:52 +0800 Subject: [PATCH 01/34] Better handling of optional virtual files (e.g., shading in Figure.grdimage) --- pygmt/clib/session.py | 10 ++++++++-- pygmt/helpers/utils.py | 18 ++++++++++++------ pygmt/src/grdimage.py | 18 +++++++----------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index f10ab72a19a..76fcd84c26f 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -5,6 +5,7 @@ Uses ctypes to wrap most of the core functions from the C API. """ import ctypes as ctp +import pathlib import sys from contextlib import contextmanager @@ -1383,6 +1384,7 @@ def virtualfile_from_data( z=None, extra_arrays=None, required_z=False, + optional_data=False, ): """ Store any data inside a virtual file. @@ -1407,6 +1409,8 @@ def virtualfile_from_data( All of these arrays must be of the same size as the x/y/z arrays. required_z : bool State whether the 'z' column is required. + optional_data : bool + State whether the 'data' is optional. Returns ------- @@ -1437,7 +1441,9 @@ def virtualfile_from_data( ... : N = 3 <7/9> <4/6> <1/3> """ - kind = data_kind(data, x, y, z, required_z=required_z) + kind = data_kind( + data, x, y, z, required_z=required_z, optional_data=optional_data + ) if check_kind == "raster" and kind not in ("file", "grid"): raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") @@ -1466,7 +1472,7 @@ def virtualfile_from_data( _data = (data,) elif kind == "file": # Useful to handle `pathlib.Path` and string file path alike - _data = (str(data),) + _data = (str(data),) if isinstance(data, pathlib.PurePath) else (data,) elif kind == "vectors": _data = [np.atleast_1d(x), np.atleast_1d(y)] if z is not None: diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index d972513ff6d..0c5d2e0bba7 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -15,13 +15,14 @@ from pygmt.exceptions import GMTInvalidInput -def data_kind(data, x=None, y=None, z=None, required_z=False): +def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=False): """ Check what kind of data is provided to a module. Possible types: * a file name provided as 'data' + * an option argument provided as 'data' * a pathlib.Path provided as 'data' * an xarray.DataArray provided as 'data' * a matrix provided as 'data' @@ -35,18 +36,21 @@ def data_kind(data, x=None, y=None, z=None, required_z=False): data : str or pathlib.Path or xarray.DataArray or {table-like} or None Pass in either a file name or :class:`pathlib.Path` to an ASCII data table, an :class:`xarray.DataArray`, a 1-D/2-D - {table-classes}. + {table-classes} or an option argument. x/y : 1-D arrays or None x and y columns as numpy arrays. z : 1-D array or None z column as numpy array. To be used optionally when x and y are given. required_z : bool State whether the 'z' column is required. + optional_data : bool + State whether the 'data' is optional. Returns ------- kind : str - One of: ``'file'``, ``'grid'``, ``'matrix'``, ``'vectors'``. + One of: ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or + ``'vectors'``. Examples -------- @@ -62,19 +66,21 @@ def data_kind(data, x=None, y=None, z=None, required_z=False): 'file' >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) 'file' + >>> data_kind(data=None, x=None, y=None, optional_data=True) + 'file' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ - if data is None and x is None and y is None: + if data is None and not optional_data and x is None and y is None: raise GMTInvalidInput("No input data provided.") if data is not None and (x is not None or y is not None or z is not None): raise GMTInvalidInput("Too much data. Use either data or x and y.") - if data is None and (x is None or y is None): + if data is None and not optional_data and (x is None or y is None): raise GMTInvalidInput("Must provide both x and y.") if data is None and required_z and z is None: raise GMTInvalidInput("Must provide x, y, and z.") - if isinstance(data, (str, pathlib.PurePath)): + if data is None or isinstance(data, (bool, int, float, str, pathlib.PurePath)): kind = "file" elif isinstance(data, xr.DataArray): kind = "grid" diff --git a/pygmt/src/grdimage.py b/pygmt/src/grdimage.py index 93e1fffb2c4..39338376182 100644 --- a/pygmt/src/grdimage.py +++ b/pygmt/src/grdimage.py @@ -180,16 +180,12 @@ def grdimage(self, grid, **kwargs): """ kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with contextlib.ExitStack() as stack: - # shading using an xr.DataArray - if kwargs.get("I") is not None and data_kind(kwargs["I"]) == "grid": - shading_context = lib.virtualfile_from_data( - check_kind="raster", data=kwargs["I"] - ) - kwargs["I"] = stack.enter_context(shading_context) - - fname = stack.enter_context(file_context) + with lib.virtualfile_from_data( + check_kind="raster", data=grid + ) as infile, lib.virtualfile_from_data( + check_kind="raster", data=kwargs.get("I"), optional_data=True + ) as shading: + kwargs["I"] = shading lib.call_module( - module="grdimage", args=build_arg_string(kwargs, infile=fname) + module="grdimage", args=build_arg_string(kwargs, infile=infile) ) From d4778002704b5157de8325f7788bec27823205dd Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 11 Apr 2023 09:57:01 +0800 Subject: [PATCH 02/34] Fix data_kind function --- pygmt/helpers/utils.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 0c5d2e0bba7..61321e3eb59 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -80,21 +80,22 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals if data is None and required_z and z is None: raise GMTInvalidInput("Must provide x, y, and z.") - if data is None or isinstance(data, (bool, int, float, str, pathlib.PurePath)): - kind = "file" - elif isinstance(data, xr.DataArray): - kind = "grid" - elif hasattr(data, "__geo_interface__"): - kind = "geojson" - elif data is not None: - if required_z and ( - getattr(data, "shape", (3, 3))[1] < 3 # np.array, pd.DataFrame - or len(getattr(data, "data_vars", (0, 1, 2))) < 3 # xr.Dataset - ): - raise GMTInvalidInput("data must provide x, y, and z columns.") - kind = "matrix" - else: + if x is not None or y is not None or z is not None: kind = "vectors" + else: + if data is None or isinstance(data, (bool, int, float, str, pathlib.PurePath)): + kind = "file" + elif isinstance(data, xr.DataArray): + kind = "grid" + elif hasattr(data, "__geo_interface__"): + kind = "geojson" + else: + if required_z and ( + getattr(data, "shape", (3, 3))[1] < 3 # np.array, pd.DataFrame + or len(getattr(data, "data_vars", (0, 1, 2))) < 3 # xr.Dataset + ): + raise GMTInvalidInput("data must provide x, y, and z columns.") + kind = "matrix" return kind From 3ff29e4f9e491ea7289d545929262b6d2ad5a743 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 11 Apr 2023 10:08:29 +0800 Subject: [PATCH 03/34] Remove unused imports --- pygmt/src/grdimage.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pygmt/src/grdimage.py b/pygmt/src/grdimage.py index 39338376182..be26f66cca9 100644 --- a/pygmt/src/grdimage.py +++ b/pygmt/src/grdimage.py @@ -1,16 +1,8 @@ """ grdimage - Plot grids or images. """ -import contextlib - from pygmt.clib import Session -from pygmt.helpers import ( - build_arg_string, - data_kind, - fmt_docstring, - kwargs_to_strings, - use_alias, -) +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias __doctest_skip__ = ["grdimage"] From 5d60a5dd8cf523fd89c1eebeda31c7b35cb1c935 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 11 Apr 2023 10:17:48 +0800 Subject: [PATCH 04/34] Use the new way in Figure.grdview --- pygmt/src/grdview.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index bd8be224eb8..696a4d1f5ba 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -155,23 +155,12 @@ def grdview(self, grid, **kwargs): """ kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - - with contextlib.ExitStack() as stack: - if kwargs.get("G") is not None: - # deal with kwargs["G"] if drapegrid is xr.DataArray - drapegrid = kwargs["G"] - if data_kind(drapegrid) in ("file", "grid"): - if data_kind(drapegrid) == "grid": - drape_context = lib.virtualfile_from_data( - check_kind="raster", data=drapegrid - ) - kwargs["G"] = stack.enter_context(drape_context) - else: - raise GMTInvalidInput( - f"Unrecognized data type for drapegrid: {type(drapegrid)}" - ) - fname = stack.enter_context(file_context) + with lib.virtualfile_from_data( + check_kind="raster", data=grid + ) as fname, lib.virtualfile_from_data( + check_kind="raster", data=kwargs.get("G"), optional_data=True + ) as drapegrid: + kwargs["G"] = drapegrid lib.call_module( module="grdview", args=build_arg_string(kwargs, infile=fname) ) From ad48c4c6c1aca50bd4a52f0307dda4ad98f7bee4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 12 Apr 2023 19:43:29 +0800 Subject: [PATCH 05/34] Change virtual file name back to fname --- pygmt/src/grdimage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/src/grdimage.py b/pygmt/src/grdimage.py index be26f66cca9..733080e539d 100644 --- a/pygmt/src/grdimage.py +++ b/pygmt/src/grdimage.py @@ -174,10 +174,10 @@ def grdimage(self, grid, **kwargs): with Session() as lib: with lib.virtualfile_from_data( check_kind="raster", data=grid - ) as infile, lib.virtualfile_from_data( + ) as fname, lib.virtualfile_from_data( check_kind="raster", data=kwargs.get("I"), optional_data=True - ) as shading: - kwargs["I"] = shading + ) as shadegrid: + kwargs["I"] = shadegrid lib.call_module( - module="grdimage", args=build_arg_string(kwargs, infile=infile) + module="grdimage", args=build_arg_string(kwargs, infile=fname) ) From f7f3d7763d82eaf3a75a8d6b114d0eefc1d6ea5a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 12 Apr 2023 19:44:45 +0800 Subject: [PATCH 06/34] Remove unused imports from grdview.py --- pygmt/src/grdview.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 696a4d1f5ba..d3f62719d98 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -1,17 +1,8 @@ """ grdview - Create a three-dimensional plot from a grid. """ -import contextlib - from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - fmt_docstring, - kwargs_to_strings, - use_alias, -) +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias __doctest_skip__ = ["grdview"] From 32c7303426eea6bc301a752793c775d87085cc43 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 12 Apr 2023 21:03:37 +0800 Subject: [PATCH 07/34] Refactor the data_kind function --- pygmt/helpers/utils.py | 71 ++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 866db8dbac7..47b08bda25c 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -10,6 +10,8 @@ import webbrowser from collections.abc import Iterable +import numpy as np +import pandas as pd import xarray as xr from pygmt.exceptions import GMTInvalidInput @@ -21,18 +23,18 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals Possible types: * a file name provided as 'data' - * an option argument provided as 'data' - * a pathlib.Path provided as 'data' - * an xarray.DataArray provided as 'data' - * a matrix provided as 'data' + * a pathlib.Path object provided as 'data' + * an xarray.DataArray object provided as 'data' + * a 2-D matrix provided as 'data' * 1-D arrays x and y (and z, optionally) + * an option argument (None, bool, int, float or str type) provided as 'data' Arguments should be ``None`` if not used. If doesn't fit any of these categories (or fits more than one), will raise an exception. Parameters ---------- - data : str or pathlib.Path or xarray.DataArray or {table-like} or None + data : str or pathlib.Path or xarray.DataArray or {table-like} or None or bool Pass in either a file name or :class:`pathlib.Path` to an ASCII data table, an :class:`xarray.DataArray`, a 1-D/2-D {table-classes} or an option argument. @@ -43,12 +45,13 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals required_z : bool State whether the 'z' column is required. optional_data : bool - State whether the 'data' is optional. + State whether 'data' is optional (useful for dealing with optional + virtual files). Returns ------- kind : str - One of: ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or + One of ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or ``'vectors'``. Examples @@ -70,31 +73,39 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ - if data is None and not optional_data and x is None and y is None: - raise GMTInvalidInput("No input data provided.") - if data is not None and (x is not None or y is not None or z is not None): - raise GMTInvalidInput("Too much data. Use either data or x and y.") - if data is None and not optional_data and (x is None or y is None): + # validate the combinations of data/x/y/z + if x is None and y is None: # both x and y are not given + if data is None and not optional_data: + raise GMTInvalidInput("No input data provides") + elif x is None or y is None: # either x or y is not given raise GMTInvalidInput("Must provide both x and y.") - if data is None and required_z and z is None: - raise GMTInvalidInput("Must provide x, y, and z.") - - if x is not None or y is not None or z is not None: - kind = "vectors" + else: # both x and y are given + if data is not None: + raise GMTInvalidInput("Too much data. Use either data or x and y.") + if required_z and z is None: + raise GMTInvalidInput("Must provide x, y, and z.") + + # determine the data kind + if isinstance(data, (bool, int, float, str, pathlib.Path)) or ( + data is None and optional_data + ): + # a null context manager will be created for "file-like" kind + kind = "file" + elif isinstance(data, xr.DataArray): + kind = "grid" + elif hasattr(data, "__geo_interface__"): + # geo-like Python object that implements ``__geo_interface__`` + # (geopandas.GeoDataFrame or shapely.geometry) + kind = "geojson" + elif data is not None: + if required_z and ( + getattr(data, "shape", (3, 3))[1] < 3 # np.array, pd.DataFrame + or len(getattr(data, "data_vars", (0, 1, 2))) < 3 # xr.Dataset + ): + raise GMTInvalidInput("data must provide x, y, and z columns.") + kind = "matrix" else: - if data is None or isinstance(data, (bool, int, float, str, pathlib.PurePath)): - kind = "file" - elif isinstance(data, xr.DataArray): - kind = "grid" - elif hasattr(data, "__geo_interface__"): - kind = "geojson" - else: - if required_z and ( - getattr(data, "shape", (3, 3))[1] < 3 # np.array, pd.DataFrame - or len(getattr(data, "data_vars", (0, 1, 2))) < 3 # xr.Dataset - ): - raise GMTInvalidInput("data must provide x, y, and z columns.") - kind = "matrix" + kind = "vectors" return kind From 2bf8fbc64b27774a6f4bc32728f268c7bfb9f6e2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 16 Apr 2023 13:44:51 +0800 Subject: [PATCH 08/34] Fix data_kind --- pygmt/helpers/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 47b08bda25c..8a7b8b5609a 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -86,8 +86,10 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals raise GMTInvalidInput("Must provide x, y, and z.") # determine the data kind - if isinstance(data, (bool, int, float, str, pathlib.Path)) or ( - data is None and optional_data + if isinstance(data, (str, pathlib.Path)): + kind = "file" + elif optional_data and ( + data is None or isinstance(data, (bool, int, float, str, pathlib.Path)) ): # a null context manager will be created for "file-like" kind kind = "file" From a8c606db917a77681d045d5c77b06243b82583ba Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 18 Apr 2023 21:36:03 +0800 Subject: [PATCH 09/34] Should check if a path is PurePath rather than Path --- pygmt/helpers/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 8a7b8b5609a..82f37244b88 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -86,10 +86,10 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals raise GMTInvalidInput("Must provide x, y, and z.") # determine the data kind - if isinstance(data, (str, pathlib.Path)): + if isinstance(data, (str, pathlib.PurePath)): kind = "file" elif optional_data and ( - data is None or isinstance(data, (bool, int, float, str, pathlib.Path)) + data is None or isinstance(data, (bool, int, float, str, pathlib.PurePath)) ): # a null context manager will be created for "file-like" kind kind = "file" From c02457764d3a1acd853675a610248abdeccb3200 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 18 Apr 2023 21:43:08 +0800 Subject: [PATCH 10/34] Fix linting --- pygmt/helpers/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 82f37244b88..1e1cce07c83 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -10,8 +10,6 @@ import webbrowser from collections.abc import Iterable -import numpy as np -import pandas as pd import xarray as xr from pygmt.exceptions import GMTInvalidInput @@ -34,7 +32,7 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals Parameters ---------- - data : str or pathlib.Path or xarray.DataArray or {table-like} or None or bool + data : str, pathlib.PurePath, None, bool, xarray.DataArray or {table-like} Pass in either a file name or :class:`pathlib.Path` to an ASCII data table, an :class:`xarray.DataArray`, a 1-D/2-D {table-classes} or an option argument. From 29a226367ac19c8168fad1142c191cab4de250b9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 19 Apr 2023 22:00:55 +0800 Subject: [PATCH 11/34] Fix data_kind function --- pygmt/helpers/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 1e1cce07c83..785742eb6a9 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -75,6 +75,8 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals if x is None and y is None: # both x and y are not given if data is None and not optional_data: raise GMTInvalidInput("No input data provides") + if data is not None and z is not None: + raise GMTInvalidInput("Too much data. Use either data or x/y/z.") elif x is None or y is None: # either x or y is not given raise GMTInvalidInput("Must provide both x and y.") else: # both x and y are given From f27f7be2bb271d4155d94ba1bfb74a9f0701ce77 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 26 Apr 2023 13:22:38 +0800 Subject: [PATCH 12/34] Fix linting --- pygmt/helpers/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 785742eb6a9..6d65336d5e0 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -25,7 +25,8 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals * an xarray.DataArray object provided as 'data' * a 2-D matrix provided as 'data' * 1-D arrays x and y (and z, optionally) - * an option argument (None, bool, int, float or str type) provided as 'data' + * an optional argument (None, bool, int, float or str type) provided as + 'data' Arguments should be ``None`` if not used. If doesn't fit any of these categories (or fits more than one), will raise an exception. From fddeb53e8f36a008b01eb63d0eaa54544a923951 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 26 Apr 2023 16:04:16 +0800 Subject: [PATCH 13/34] data_kind now returns 'null' for other cases --- pygmt/clib/session.py | 11 ++++++----- pygmt/helpers/utils.py | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 5c4c98408be..b9c738e26e9 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -5,7 +5,6 @@ Uses ctypes to wrap most of the core functions from the C API. """ import ctypes as ctp -import pathlib import sys from contextlib import contextmanager, nullcontext @@ -1536,19 +1535,21 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, optional_data=optional_data ) - if check_kind == "raster" and kind not in ("file", "grid"): + if check_kind == "raster" and kind not in ("file", "grid", "null"): raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") if check_kind == "vector" and kind not in ( "file", "matrix", "vectors", "geojson", + "null", ): raise GMTInvalidInput(f"Unrecognized data type for vector: {type(data)}") # Decide which virtualfile_from_ function to use _virtualfile_from = { "file": nullcontext, + "null": nullcontext, "geojson": tempfile_from_geojson, "grid": self.virtualfile_from_grid, # Note: virtualfile_from_matrix is not used because a matrix can be @@ -1559,11 +1560,11 @@ def virtualfile_from_data( }[kind] # Ensure the data is an iterable (Python list or tuple) - if kind in ("geojson", "grid"): + if kind in ("geojson", "grid", "null"): _data = (data,) elif kind == "file": - # Useful to handle `pathlib.Path` and string file path alike - _data = (str(data),) if isinstance(data, pathlib.PurePath) else (data,) + # Useful to handle `pathlib.PurePath` and string file path alike + _data = (str(data),) elif kind == "vectors": _data = [np.atleast_1d(x), np.atleast_1d(y)] if z is not None: diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 6d65336d5e0..bf5db30a0ef 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -21,12 +21,11 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals Possible types: * a file name provided as 'data' - * a pathlib.Path object provided as 'data' + * a pathlib.PurePath object provided as 'data' * an xarray.DataArray object provided as 'data' * a 2-D matrix provided as 'data' * 1-D arrays x and y (and z, optionally) - * an optional argument (None, bool, int, float or str type) provided as - 'data' + * an optional argument (None, bool, int or float) provided as 'data' Arguments should be ``None`` if not used. If doesn't fit any of these categories (or fits more than one), will raise an exception. @@ -50,8 +49,8 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals Returns ------- kind : str - One of ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or - ``'vectors'``. + One of ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, ``'null'``, + or ``'vectors'``. Examples -------- @@ -68,7 +67,11 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) 'file' >>> data_kind(data=None, x=None, y=None, optional_data=True) - 'file' + 'null' + >>> data_kind(data=2.0, x=None, y=None, optional_data=True) + 'null' + >>> data_kind(data=True, x=None, y=None, optional_data=True) + 'null' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ @@ -89,11 +92,9 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals # determine the data kind if isinstance(data, (str, pathlib.PurePath)): kind = "file" - elif optional_data and ( - data is None or isinstance(data, (bool, int, float, str, pathlib.PurePath)) - ): - # a null context manager will be created for "file-like" kind - kind = "file" + elif optional_data and (data is None or isinstance(data, (bool, int, float))): + # a nullcontext will be created for "null" kind + kind = "null" elif isinstance(data, xr.DataArray): kind = "grid" elif hasattr(data, "__geo_interface__"): From 014eeb432c1308236bb5e3d1ecc501b4a8828bb4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 26 Apr 2023 17:57:06 +0800 Subject: [PATCH 14/34] Disable a pylint warning --- pygmt/helpers/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index bf5db30a0ef..5b0221aa84b 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -75,6 +75,7 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ + # pylint: disable=too-many-branches # validate the combinations of data/x/y/z if x is None and y is None: # both x and y are not given if data is None and not optional_data: From a586a6ed4ca2d6e31abdb921eabca574e2649d08 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 26 Apr 2023 18:09:41 +0800 Subject: [PATCH 15/34] grdview: Check if the given drapegrid is valid --- pygmt/src/grdview.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index d3f62719d98..075616033f5 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -2,7 +2,14 @@ grdview - Create a three-dimensional plot from a grid. """ from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + fmt_docstring, + kwargs_to_strings, + use_alias, +) __doctest_skip__ = ["grdview"] @@ -145,6 +152,12 @@ def grdview(self, grid, **kwargs): >>> fig.show() """ kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + + if kwargs.get("G") is not None and data_kind(kwargs["G"]) not in ("file", "grid"): + raise GMTInvalidInput( + f"Unrecognized data type for drapegrid: {type(kwargs['G'])}" + ) + with Session() as lib: with lib.virtualfile_from_data( check_kind="raster", data=grid From 9a43dc8edab2b5b0f907b84e0ff740cda2c4c80e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 4 Jul 2023 15:22:47 +0800 Subject: [PATCH 16/34] Combine file and null kinds into file_or_arg --- pygmt/clib/session.py | 16 ++++++---------- pygmt/helpers/utils.py | 19 +++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index b9c738e26e9..03790d683c6 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -5,6 +5,7 @@ Uses ctypes to wrap most of the core functions from the C API. """ import ctypes as ctp +import pathlib import sys from contextlib import contextmanager, nullcontext @@ -1535,21 +1536,19 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, optional_data=optional_data ) - if check_kind == "raster" and kind not in ("file", "grid", "null"): + if check_kind == "raster" and kind not in ("file_or_arg", "grid"): raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") if check_kind == "vector" and kind not in ( - "file", + "file_or_arg", "matrix", "vectors", "geojson", - "null", ): raise GMTInvalidInput(f"Unrecognized data type for vector: {type(data)}") # Decide which virtualfile_from_ function to use _virtualfile_from = { - "file": nullcontext, - "null": nullcontext, + "file_or_arg": nullcontext, "geojson": tempfile_from_geojson, "grid": self.virtualfile_from_grid, # Note: virtualfile_from_matrix is not used because a matrix can be @@ -1560,11 +1559,8 @@ def virtualfile_from_data( }[kind] # Ensure the data is an iterable (Python list or tuple) - if kind in ("geojson", "grid", "null"): - _data = (data,) - elif kind == "file": - # Useful to handle `pathlib.PurePath` and string file path alike - _data = (str(data),) + if kind in ("geojson", "grid", "file_or_arg"): + _data = (data,) if not isinstance(data, pathlib.PurePath) else (str(data),) elif kind == "vectors": _data = [np.atleast_1d(x), np.atleast_1d(y)] if z is not None: diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 5b0221aa84b..494616aced5 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -63,15 +63,15 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals >>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None) 'matrix' >>> data_kind(data="my-data-file.txt", x=None, y=None) - 'file' + 'file_or_arg' >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) - 'file' + 'file_or_arg' >>> data_kind(data=None, x=None, y=None, optional_data=True) - 'null' + 'file_or_arg' >>> data_kind(data=2.0, x=None, y=None, optional_data=True) - 'null' + 'file_or_arg' >>> data_kind(data=True, x=None, y=None, optional_data=True) - 'null' + 'file_or_arg' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ @@ -91,11 +91,10 @@ def data_kind(data, x=None, y=None, z=None, required_z=False, optional_data=Fals raise GMTInvalidInput("Must provide x, y, and z.") # determine the data kind - if isinstance(data, (str, pathlib.PurePath)): - kind = "file" - elif optional_data and (data is None or isinstance(data, (bool, int, float))): - # a nullcontext will be created for "null" kind - kind = "null" + if isinstance(data, (str, pathlib.PurePath)) or ( + optional_data and (data is None or isinstance(data, (bool, int, float))) + ): + kind = "file_or_arg" elif isinstance(data, xr.DataArray): kind = "grid" elif hasattr(data, "__geo_interface__"): From 846bee66fe5f7bf205f98b53c0aa8aee76cfc22f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 5 Jul 2023 20:34:26 +0800 Subject: [PATCH 17/34] Rename optional_data to required_data --- pygmt/clib/session.py | 6 +++--- pygmt/helpers/utils.py | 21 +++++++++++---------- pygmt/src/grdimage.py | 2 +- pygmt/src/grdview.py | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 03790d683c6..d2e82619ceb 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1475,7 +1475,7 @@ def virtualfile_from_data( z=None, extra_arrays=None, required_z=False, - optional_data=False, + required_data=True, ): """ Store any data inside a virtual file. @@ -1500,7 +1500,7 @@ def virtualfile_from_data( All of these arrays must be of the same size as the x/y/z arrays. required_z : bool State whether the 'z' column is required. - optional_data : bool + required_data : bool State whether the 'data' is optional. Returns @@ -1533,7 +1533,7 @@ def virtualfile_from_data( : N = 3 <7/9> <4/6> <1/3> """ kind = data_kind( - data, x, y, z, required_z=required_z, optional_data=optional_data + data, x, y, z, required_z=required_z, required_data=required_data ) if check_kind == "raster" and kind not in ("file_or_arg", "grid"): diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index d317ea7a760..7230d67c961 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -15,7 +15,7 @@ def _validate_data_input( - data=None, x=None, y=None, z=None, required_z=False, optional_data=False, kind=None + data=None, x=None, y=None, z=None, required_z=False, required_data=True, kind=None ): """ Check if the combination of data/x/y/z is valid. @@ -25,6 +25,7 @@ def _validate_data_input( >>> _validate_data_input(data="infile") >>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6]) >>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6], z=[7, 8, 9]) + >>> _validate_data_input(data=None, required_data=False) >>> _validate_data_input() Traceback (most recent call last): ... @@ -61,7 +62,7 @@ def _validate_data_input( """ if data is None: # data is None # both x and y are None and data is not optional - if x is None and y is None and not optional_data: + if x is None and y is None and required_data: raise GMTInvalidInput("No input data provided.") # either x or y is None if x is None or y is None: @@ -83,7 +84,7 @@ def _validate_data_input( raise GMTInvalidInput("data must provide x, y, and z columns.") -def data_kind(data=None, x=None, y=None, z=None, required_z=False, optional_data=False): +def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data=True): """ Check what kind of data is provided to a module. @@ -111,8 +112,8 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, optional_data z column as numpy array. To be used optionally when x and y are given. required_z : bool State whether the 'z' column is required. - optional_data : bool - State whether 'data' is optional (useful for dealing with optional + required_data : bool + State whether 'data' is required (useful for dealing with optional virtual files). Returns @@ -135,18 +136,18 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, optional_data 'file_or_arg' >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) 'file_or_arg' - >>> data_kind(data=None, x=None, y=None, optional_data=True) + >>> data_kind(data=None, x=None, y=None, required_data=False) 'file_or_arg' - >>> data_kind(data=2.0, x=None, y=None, optional_data=True) + >>> data_kind(data=2.0, x=None, y=None, required_data=False) 'file_or_arg' - >>> data_kind(data=True, x=None, y=None, optional_data=True) + >>> data_kind(data=True, x=None, y=None, required_data=False) 'file_or_arg' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ # determine the data kind if isinstance(data, (str, pathlib.PurePath)) or ( - optional_data and (data is None or isinstance(data, (bool, int, float))) + not required_data and (data is None or isinstance(data, (bool, int, float))) ): kind = "file_or_arg" elif isinstance(data, xr.DataArray): @@ -165,7 +166,7 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, optional_data y=y, z=z, required_z=required_z, - optional_data=optional_data, + required_data=required_data, kind=kind, ) return kind diff --git a/pygmt/src/grdimage.py b/pygmt/src/grdimage.py index 733080e539d..1aff280fc2e 100644 --- a/pygmt/src/grdimage.py +++ b/pygmt/src/grdimage.py @@ -175,7 +175,7 @@ def grdimage(self, grid, **kwargs): with lib.virtualfile_from_data( check_kind="raster", data=grid ) as fname, lib.virtualfile_from_data( - check_kind="raster", data=kwargs.get("I"), optional_data=True + check_kind="raster", data=kwargs.get("I"), optional_data=False ) as shadegrid: kwargs["I"] = shadegrid lib.call_module( diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 075616033f5..5cd39ecd26b 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -162,7 +162,7 @@ def grdview(self, grid, **kwargs): with lib.virtualfile_from_data( check_kind="raster", data=grid ) as fname, lib.virtualfile_from_data( - check_kind="raster", data=kwargs.get("G"), optional_data=True + check_kind="raster", data=kwargs.get("G"), required_data=False ) as drapegrid: kwargs["G"] = drapegrid lib.call_module( From 46483e942e7a917e282e40afb7b5c56dbcf559c0 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 6 Jul 2023 09:02:22 +0800 Subject: [PATCH 18/34] Fix the logical of check required_data --- pygmt/helpers/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 7230d67c961..5c9a0c322e2 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -61,14 +61,12 @@ def _validate_data_input( If the data input is not valid. """ if data is None: # data is None - # both x and y are None and data is not optional - if x is None and y is None and required_data: - raise GMTInvalidInput("No input data provided.") - # either x or y is None - if x is None or y is None: + if x is None and y is None: # both x and y are None + if required_data: # data is not optional + raise GMTInvalidInput("No input data provided.") + if x is None or y is None: # either x or y is None raise GMTInvalidInput("Must provide both x and y.") - # both x and y are not None, now check z - if required_z and z is None: + if required_z and z is None: # both x and y are not None, now check z raise GMTInvalidInput("Must provide x, y, and z.") else: # data is not None if x is not None or y is not None or z is not None: From 7a72d5d62500323813a8803e0dbcf0ce973f6668 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 6 Jul 2023 15:53:54 +0800 Subject: [PATCH 19/34] Apply suggestions from code review Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/clib/session.py | 3 ++- pygmt/helpers/utils.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index d2e82619ceb..053934b9411 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1501,7 +1501,8 @@ def virtualfile_from_data( required_z : bool State whether the 'z' column is required. required_data : bool - State whether the 'data' is optional. + State whether 'data' is required (useful for dealing with optional + virtual files). Returns ------- diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 5c9a0c322e2..e35f96534cc 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -117,8 +117,8 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data Returns ------- kind : str - One of ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, ``'null'``, - or ``'vectors'``. + One of ``'file_or_arg'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or + ``'vectors'``. Examples -------- From 15aec708b1677d62a5dc4ef5a062453db60d1679 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 08:44:39 +0800 Subject: [PATCH 20/34] Fix the logic in _validate_input_data --- pygmt/helpers/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index e35f96534cc..51c69ee842e 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -64,7 +64,7 @@ def _validate_data_input( if x is None and y is None: # both x and y are None if required_data: # data is not optional raise GMTInvalidInput("No input data provided.") - if x is None or y is None: # either x or y is None + elif x is None or y is None: # either x or y is None raise GMTInvalidInput("Must provide both x and y.") if required_z and z is None: # both x and y are not None, now check z raise GMTInvalidInput("Must provide x, y, and z.") From 2fcd515549d8731f49c797c2717aeebe7309878e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 08:46:49 +0800 Subject: [PATCH 21/34] Fix grdimage --- pygmt/src/grdimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdimage.py b/pygmt/src/grdimage.py index 1aff280fc2e..d036453985e 100644 --- a/pygmt/src/grdimage.py +++ b/pygmt/src/grdimage.py @@ -175,7 +175,7 @@ def grdimage(self, grid, **kwargs): with lib.virtualfile_from_data( check_kind="raster", data=grid ) as fname, lib.virtualfile_from_data( - check_kind="raster", data=kwargs.get("I"), optional_data=False + check_kind="raster", data=kwargs.get("I"), required_data=False ) as shadegrid: kwargs["I"] = shadegrid lib.call_module( From 5faa05dc27b458e7ba675e02704faefcf05d9086 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 08:58:29 +0800 Subject: [PATCH 22/34] Change file_or_arg back to file --- pygmt/clib/session.py | 8 ++++---- pygmt/helpers/utils.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 053934b9411..3d515cd468c 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1537,10 +1537,10 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, required_data=required_data ) - if check_kind == "raster" and kind not in ("file_or_arg", "grid"): + if check_kind == "raster" and kind not in ("file", "grid"): raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") if check_kind == "vector" and kind not in ( - "file_or_arg", + "file", "matrix", "vectors", "geojson", @@ -1549,7 +1549,7 @@ def virtualfile_from_data( # Decide which virtualfile_from_ function to use _virtualfile_from = { - "file_or_arg": nullcontext, + "file": nullcontext, "geojson": tempfile_from_geojson, "grid": self.virtualfile_from_grid, # Note: virtualfile_from_matrix is not used because a matrix can be @@ -1560,7 +1560,7 @@ def virtualfile_from_data( }[kind] # Ensure the data is an iterable (Python list or tuple) - if kind in ("geojson", "grid", "file_or_arg"): + if kind in ("geojson", "grid", "file"): _data = (data,) if not isinstance(data, pathlib.PurePath) else (str(data),) elif kind == "vectors": _data = [np.atleast_1d(x), np.atleast_1d(y)] diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 51c69ee842e..a21f0164fcf 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -117,7 +117,7 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data Returns ------- kind : str - One of ``'file_or_arg'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or + One of ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or ``'vectors'``. Examples @@ -131,23 +131,23 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data >>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None) 'matrix' >>> data_kind(data="my-data-file.txt", x=None, y=None) - 'file_or_arg' + 'file' >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) - 'file_or_arg' + 'file' >>> data_kind(data=None, x=None, y=None, required_data=False) - 'file_or_arg' + 'file' >>> data_kind(data=2.0, x=None, y=None, required_data=False) - 'file_or_arg' + 'file' >>> data_kind(data=True, x=None, y=None, required_data=False) - 'file_or_arg' + 'file' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ # determine the data kind - if isinstance(data, (str, pathlib.PurePath)) or ( - not required_data and (data is None or isinstance(data, (bool, int, float))) - ): - kind = "file_or_arg" + if isinstance(data, (str, pathlib.PurePath)): + kind = "file" + elif not required_data and (data is None or isinstance(data, (bool, int, float))): + kind = "arg" elif isinstance(data, xr.DataArray): kind = "grid" elif hasattr(data, "__geo_interface__"): From 0433de9bab490bbfb6fbf4a0d6597f61511e6e7f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 09:07:30 +0800 Subject: [PATCH 23/34] Remove unecessary data check --- pygmt/src/grdview.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 5cd39ecd26b..9ee97559d25 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -152,12 +152,6 @@ def grdview(self, grid, **kwargs): >>> fig.show() """ kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - - if kwargs.get("G") is not None and data_kind(kwargs["G"]) not in ("file", "grid"): - raise GMTInvalidInput( - f"Unrecognized data type for drapegrid: {type(kwargs['G'])}" - ) - with Session() as lib: with lib.virtualfile_from_data( check_kind="raster", data=grid From 4e0b0218f1f5e133abff2f474b0ab006d0268692 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 09:10:44 +0800 Subject: [PATCH 24/34] Fix styling --- pygmt/src/grdview.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 9ee97559d25..7b6572c61d5 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -3,13 +3,7 @@ """ from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - fmt_docstring, - kwargs_to_strings, - use_alias, -) +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias __doctest_skip__ = ["grdview"] From c6bc376f1f1c5ca53255b47dce8ca1a67f6df3e2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 10:09:27 +0800 Subject: [PATCH 25/34] Fix --- pygmt/clib/session.py | 3 ++- pygmt/helpers/utils.py | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 3d515cd468c..f29ae57762e 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1550,6 +1550,7 @@ def virtualfile_from_data( # Decide which virtualfile_from_ function to use _virtualfile_from = { "file": nullcontext, + "arg": nullcontext, "geojson": tempfile_from_geojson, "grid": self.virtualfile_from_grid, # Note: virtualfile_from_matrix is not used because a matrix can be @@ -1560,7 +1561,7 @@ def virtualfile_from_data( }[kind] # Ensure the data is an iterable (Python list or tuple) - if kind in ("geojson", "grid", "file"): + if kind in ("geojson", "grid", "file", "arg"): _data = (data,) if not isinstance(data, pathlib.PurePath) else (str(data),) elif kind == "vectors": _data = [np.atleast_1d(x), np.atleast_1d(y)] diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index a21f0164fcf..6e37dc18e7c 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -117,8 +117,8 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data Returns ------- kind : str - One of ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, or - ``'vectors'``. + One of ``'arg'``, ``'file'``, ``'grid'``, ``'geojson'``, ``'matrix'``, + or ``'vectors'``. Examples -------- @@ -135,18 +135,18 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) 'file' >>> data_kind(data=None, x=None, y=None, required_data=False) - 'file' + 'arg' >>> data_kind(data=2.0, x=None, y=None, required_data=False) - 'file' + 'arg' >>> data_kind(data=True, x=None, y=None, required_data=False) - 'file' + 'arg' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' """ # determine the data kind if isinstance(data, (str, pathlib.PurePath)): kind = "file" - elif not required_data and (data is None or isinstance(data, (bool, int, float))): + elif isinstance(data, (bool, int, float)) or (data is None and not required_data): kind = "arg" elif isinstance(data, xr.DataArray): kind = "grid" From cc02b96b890c9bb86d9e209b30abc30c4ebcb41e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 10:17:16 +0800 Subject: [PATCH 26/34] One more fix --- pygmt/clib/session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index f29ae57762e..4da97f9e7ee 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1537,13 +1537,14 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, required_data=required_data ) - if check_kind == "raster" and kind not in ("file", "grid"): + if check_kind == "raster" and kind not in ("file", "grid", "arg"): raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") if check_kind == "vector" and kind not in ( "file", "matrix", "vectors", "geojson", + "arg", ): raise GMTInvalidInput(f"Unrecognized data type for vector: {type(data)}") From f3df8fc424470fd1221f4118317e4d4852dadd7e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 22 Jul 2023 10:38:52 +0800 Subject: [PATCH 27/34] Fix a linting issue --- pygmt/src/grdview.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 7b6572c61d5..57c8840ccca 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -2,7 +2,6 @@ grdview - Create a three-dimensional plot from a grid. """ from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias __doctest_skip__ = ["grdview"] From 83a63607de9fe67336c1b4499d9716623d05f833 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 2 Aug 2023 13:35:56 +0800 Subject: [PATCH 28/34] Fix the logic of checking kinds --- pygmt/clib/session.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 4da97f9e7ee..c8d51993da9 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1537,16 +1537,18 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, required_data=required_data ) - if check_kind == "raster" and kind not in ("file", "grid", "arg"): - raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") - if check_kind == "vector" and kind not in ( - "file", - "matrix", - "vectors", - "geojson", - "arg", - ): - raise GMTInvalidInput(f"Unrecognized data type for vector: {type(data)}") + if check_kind == "raster": + valid_kinds = ("file", "grid") + elif check_kind == "vector": + valid_kinds = ("file", "matrix", "vectors", "geojson") + else: + valid_kinds = () + if required_data is False: + valid_kinds += ("arg",) + if kind not in valid_kinds: + raise GMTInvalidInput( + f"Unrecognized data type for {check_kind}: {type(data)}" + ) # Decide which virtualfile_from_ function to use _virtualfile_from = { From be93bd42dcb2615013a438ba61c110c9a4cc40d1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 2 Aug 2023 14:32:29 +0800 Subject: [PATCH 29/34] check_kind should never be None --- pygmt/clib/session.py | 4 +++- pygmt/src/dimfilter.py | 2 +- pygmt/tests/test_clib.py | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index c8d51993da9..f330d7ac8b4 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1542,7 +1542,9 @@ def virtualfile_from_data( elif check_kind == "vector": valid_kinds = ("file", "matrix", "vectors", "geojson") else: - valid_kinds = () + raise GMTInvalidInput( + "Invalid check_kind. Should be either 'raster' or 'vector'." + ) if required_data is False: valid_kinds += ("arg",) if kind not in valid_kinds: diff --git a/pygmt/src/dimfilter.py b/pygmt/src/dimfilter.py index ba4e39ce1bd..ddfd1d31e0c 100644 --- a/pygmt/src/dimfilter.py +++ b/pygmt/src/dimfilter.py @@ -154,7 +154,7 @@ def dimfilter(grid, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind=None, data=grid) + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) with file_context as infile: if (outgrid := kwargs.get("G")) is None: kwargs["G"] = outgrid = tmpfile.name # output to tmpfile diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index 6ad698c13ae..38a77f5f064 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -439,7 +439,9 @@ def test_virtualfile_from_data_required_z_matrix(array_func, kind): ) data = array_func(dataframe) with clib.Session() as lib: - with lib.virtualfile_from_data(data=data, required_z=True) as vfile: + with lib.virtualfile_from_data( + data=data, required_z=True, check_kind="vector" + ) as vfile: with GMTTempFile() as outfile: lib.call_module("info", f"{vfile} ->{outfile.name}") output = outfile.read(keep_tabs=True) @@ -461,7 +463,9 @@ def test_virtualfile_from_data_required_z_matrix_missing(): data = np.ones((5, 2)) with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - with lib.virtualfile_from_data(data=data, required_z=True): + with lib.virtualfile_from_data( + data=data, required_z=True, check_kind="vector" + ): pass From 73c31e214e7b994afdc2dc1cdb766ca1fca529f3 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 2 Aug 2023 14:47:30 +0800 Subject: [PATCH 30/34] Add more inline doctests for required_z --- pygmt/helpers/utils.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 6e37dc18e7c..06f4cc62382 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -42,6 +42,30 @@ def _validate_data_input( Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: Must provide x, y, and z. + >>> import numpy as np + >>> import pandas as pd + >>> import xarray as xr + >>> data = np.arange(8).reshape((4, 2)) + >>> _validate_data_input(data=data, required_z=True, kind="matrix") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns. + >>> _validate_data_input( + ... data=pd.DataFrame(data, columns=["x", "y"]), + ... required_z=True, + ... kind="matrix", + ... ) + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns. + >>> _validate_data_input( + ... data=xr.Dataset(pd.DataFrame(data, columns=["x", "y"])), + ... required_z=True, + ... kind="matrix", + ... ) + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns. >>> _validate_data_input(data="infile", x=[1, 2, 3]) Traceback (most recent call last): ... From e64b6af9356032668c0949969761bd41c771198a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 4 Aug 2023 14:27:08 +0800 Subject: [PATCH 31/34] Apply suggestions from code review Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/clib/session.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index f330d7ac8b4..348fe1c9421 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1501,8 +1501,8 @@ def virtualfile_from_data( required_z : bool State whether the 'z' column is required. required_data : bool - State whether 'data' is required (useful for dealing with optional - virtual files). + Set to True when 'data' is required, or False when dealing with + optional virtual files. [Default is True]. Returns ------- @@ -1537,16 +1537,15 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, required_data=required_data ) + valid_kinds = ("file", "arg") if required_data is False else ("file",) if check_kind == "raster": - valid_kinds = ("file", "grid") + valid_kinds += ("grid",) elif check_kind == "vector": - valid_kinds = ("file", "matrix", "vectors", "geojson") + valid_kinds += ("matrix", "vectors", "geojson") else: raise GMTInvalidInput( "Invalid check_kind. Should be either 'raster' or 'vector'." ) - if required_data is False: - valid_kinds += ("arg",) if kind not in valid_kinds: raise GMTInvalidInput( f"Unrecognized data type for {check_kind}: {type(data)}" From b9d4f609faf2ad5dbbe89be346eb9a90d05997d6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 4 Aug 2023 14:28:02 +0800 Subject: [PATCH 32/34] Update pygmt/helpers/utils.py Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/helpers/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 06f4cc62382..e52197012be 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -135,8 +135,8 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data required_z : bool State whether the 'z' column is required. required_data : bool - State whether 'data' is required (useful for dealing with optional - virtual files). + Set to True when 'data' is required, or False when dealing with + optional virtual files. [Default is True]. Returns ------- From 9b31e230fa0f4640e3a0a5bd12f5aad1c58936c0 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 4 Aug 2023 14:35:57 +0800 Subject: [PATCH 33/34] Check kind only if chekc_kind is not None --- pygmt/clib/session.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 348fe1c9421..f30eefdfc16 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1537,19 +1537,16 @@ def virtualfile_from_data( data, x, y, z, required_z=required_z, required_data=required_data ) - valid_kinds = ("file", "arg") if required_data is False else ("file",) - if check_kind == "raster": - valid_kinds += ("grid",) - elif check_kind == "vector": - valid_kinds += ("matrix", "vectors", "geojson") - else: - raise GMTInvalidInput( - "Invalid check_kind. Should be either 'raster' or 'vector'." - ) - if kind not in valid_kinds: - raise GMTInvalidInput( - f"Unrecognized data type for {check_kind}: {type(data)}" - ) + if check_kind: + valid_kinds = ("file", "arg") if required_data is False else ("file",) + if check_kind == "raster": + valid_kinds += ("grid",) + elif check_kind == "vector": + valid_kinds += ("matrix", "vectors", "geojson") + if kind not in valid_kinds: + raise GMTInvalidInput( + f"Unrecognized data type for {check_kind}: {type(data)}" + ) # Decide which virtualfile_from_ function to use _virtualfile_from = { From 0ed6248bb76bda675c5d87c51b123b48860b7213 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 4 Aug 2023 14:48:09 +0800 Subject: [PATCH 34/34] Improve the docstring of check_kind --- pygmt/clib/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index f30eefdfc16..069a762001d 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1486,7 +1486,7 @@ def virtualfile_from_data( Parameters ---------- - check_kind : str + check_kind : str or None Used to validate the type of data that can be passed in. Choose from 'raster', 'vector', or None. Default is None (no validation). data : str or pathlib.Path or xarray.DataArray or {table-like} or None