From ba58ed19b387bc072e1501f90b50261cf4ad99e0 Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Thu, 5 Jul 2018 11:52:33 -1000 Subject: [PATCH 1/9] Rename LibGMT to Session --- gmt/__init__.py | 4 +- gmt/base_plotting.py | 12 ++-- gmt/clib/__init__.py | 4 +- gmt/clib/{core.py => session.py} | 66 ++++++++--------- gmt/figure.py | 8 +-- gmt/modules.py | 8 +-- gmt/session_management.py | 6 +- gmt/tests/test_clib.py | 104 +++++++++++++-------------- gmt/tests/test_session_management.py | 4 +- 9 files changed, 108 insertions(+), 108 deletions(-) rename gmt/clib/{core.py => session.py} (96%) diff --git a/gmt/__init__.py b/gmt/__init__.py index c5ed977e798..8317cdc647b 100644 --- a/gmt/__init__.py +++ b/gmt/__init__.py @@ -35,7 +35,7 @@ def print_libgmt_info(): ``libgmt`` shared library, and GMT directories. """ import shutil - from .clib import LibGMT + from .clib import Session columns = shutil.get_terminal_size().columns title = "Currently loaded libgmt" @@ -43,7 +43,7 @@ def print_libgmt_info(): right = left + (columns - (2 * left + len(title) + 2)) header = " ".join(["=" * left, title, "=" * right]) - with LibGMT() as lib: + with Session() as lib: lines = [header] for key in sorted(lib.info): lines.append("{}: {}".format(key, lib.info[key])) diff --git a/gmt/base_plotting.py b/gmt/base_plotting.py index e324785e90d..df1b51d7596 100644 --- a/gmt/base_plotting.py +++ b/gmt/base_plotting.py @@ -2,7 +2,7 @@ Base class with plot generating commands. Does not define any special non-GMT methods (savefig, show, etc). """ -from .clib import LibGMT +from .clib import Session from .exceptions import GMTInvalidInput from .helpers import ( build_arg_string, @@ -122,7 +122,7 @@ def coast(self, **kwargs): """ kwargs = self._preprocess(**kwargs) - with LibGMT() as lib: + with Session() as lib: lib.call_module("coast", build_arg_string(kwargs)) @fmt_docstring @@ -146,7 +146,7 @@ def grdimage(self, grid, **kwargs): """ kwargs = self._preprocess(**kwargs) kind = data_kind(grid, None, None) - with LibGMT() as lib: + with Session() as lib: if kind == "file": file_context = dummy_context(grid) elif kind == "grid": @@ -257,7 +257,7 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): ) extra_arrays.append(sizes) - with LibGMT() as lib: + with Session() as lib: # Choose how data will be passed in to the module if kind == "file": file_context = dummy_context(data) @@ -316,7 +316,7 @@ def basemap(self, **kwargs): raise GMTInvalidInput("At least one of B, L, or T must be specified.") if "D" in kwargs and "F" not in kwargs: raise GMTInvalidInput("Option D requires F to be specified as well.") - with LibGMT() as lib: + with Session() as lib: lib.call_module("basemap", build_arg_string(kwargs)) @fmt_docstring @@ -351,5 +351,5 @@ def logo(self, **kwargs): kwargs = self._preprocess(**kwargs) if "D" not in kwargs: raise GMTInvalidInput("Option D must be specified.") - with LibGMT() as lib: + with Session() as lib: lib.call_module("logo", build_arg_string(kwargs)) diff --git a/gmt/clib/__init__.py b/gmt/clib/__init__.py index 59d041a2ca8..7d508095804 100644 --- a/gmt/clib/__init__.py +++ b/gmt/clib/__init__.py @@ -1,9 +1,9 @@ """ Low-level wrapper for the GMT C API. -The :class:`gmt.clib.LibGMT` class wraps the GMT C shared library (``libgmt``) +The :class:`gmt.clib.Session` class wraps the GMT C shared library (``libgmt``) with a pythonic interface. Access to the C library is done through :py:mod:`ctypes`. """ -from .core import LibGMT +from .session import Session diff --git a/gmt/clib/core.py b/gmt/clib/session.py similarity index 96% rename from gmt/clib/core.py rename to gmt/clib/session.py index dd578e706aa..833e47d1530 100644 --- a/gmt/clib/core.py +++ b/gmt/clib/session.py @@ -23,7 +23,7 @@ ) -class LibGMT: # pylint: disable=too-many-instance-attributes +class Session: # pylint: disable=too-many-instance-attributes """ Load and access the GMT shared library (libgmt). @@ -39,7 +39,7 @@ class LibGMT: # pylint: disable=too-many-instance-attributes If creating GMT data structures to communicate data, put that code inside this context manager to reuse the same session. - Requires a minimum version of GMT (see ``LibGMT.required_version``). Will + Requires a minimum version of GMT (see ``Session.required_version``). Will check for the version when entering the ``with`` block. A ``GMTVersionError`` exception will be raised if the minimum version requirements aren't met. @@ -61,7 +61,7 @@ class LibGMT: # pylint: disable=too-many-instance-attributes Examples -------- - >>> with LibGMT() as lib: + >>> with Session() as lib: ... lib.call_module('figure', 'my-figure') """ @@ -178,7 +178,7 @@ def get_libgmt_func(self, name, argtypes=None, restype=None): -------- >>> from ctypes import c_void_p, c_int - >>> with LibGMT() as lib: + >>> with Session() as lib: ... func = lib.get_libgmt_func('GMT_Destroy_Session', ... argtypes=[c_void_p], restype=c_int) >>> type(func) @@ -234,7 +234,7 @@ def create_session(self, session_name): cannot be accessed directly. Remember to terminate the current session using - :func:`gmt.clib.LibGMT.destroy_session` before creating a new one. + :func:`gmt.clib.Session.destroy_session` before creating a new one. Parameters ---------- @@ -307,7 +307,7 @@ def destroy_session(self, session): ---------- session : C void pointer (returned by ctypes as an integer) The active session object produced by - :func:`gmt.clib.LibGMT.create_session`. + :func:`gmt.clib.Session.create_session`. libgmt : :py:class:`ctypes.CDLL` The :py:class:`ctypes.CDLL` instance for the libgmt shared library. @@ -567,7 +567,7 @@ def _parse_constant(self, constant, valid, valid_modifiers=None): The GMT C API takes certain defined constants, like ``'GMT_IS_GRID'``, that need to be validated and converted to integer values using - :meth:`~gmt.clib.LibGMT.get_constant`. + :meth:`~gmt.clib.Session.get_constant`. The constants can also take a modifier by appending another constant name, e.g. ``'GMT_IS_GRID|GMT_VIA_MATRIX'``. The two parts must be @@ -647,12 +647,12 @@ def _check_dtype_and_dim(self, array, ndim): >>> import numpy as np >>> data = np.array([1, 2, 3], dtype='float64') - >>> with LibGMT() as lib: + >>> with Session() as lib: ... gmttype = lib._check_dtype_and_dim(data, ndim=1) ... gmttype == lib.get_constant('GMT_DOUBLE') True >>> data = np.ones((5, 2), dtype='float32') - >>> with LibGMT() as lib: + >>> with Session() as lib: ... gmttype = lib._check_dtype_and_dim(data, ndim=2) ... gmttype == lib.get_constant('GMT_FLOAT') True @@ -675,7 +675,7 @@ def put_vector(self, dataset, column, vector): Use this functions to attach numpy array data to a GMT dataset and pass it to GMT modules. Wraps ``GMT_Put_Vector``. - The dataset must be created by :meth:`~gmt.clib.LibGMT.create_data` + The dataset must be created by :meth:`~gmt.clib.Session.create_data` first. Use ``family='GMT_IS_DATASET|GMT_VIA_VECTOR'``. Not at all numpy dtypes are supported, only: float64, float32, int64, @@ -691,7 +691,7 @@ def put_vector(self, dataset, column, vector): ---------- dataset : :py:class:`ctypes.c_void_p` The ctypes void pointer to a ``GMT_Dataset``. Create it with - :meth:`~gmt.clib.LibGMT.create_data`. + :meth:`~gmt.clib.Session.create_data`. column : int The column number of this vector in the dataset (starting from 0). vector : numpy 1d-array @@ -739,7 +739,7 @@ def put_matrix(self, dataset, matrix, pad=0): Use this functions to attach numpy array data to a GMT dataset and pass it to GMT modules. Wraps ``GMT_Put_Matrix``. - The dataset must be created by :meth:`~gmt.clib.LibGMT.create_data` + The dataset must be created by :meth:`~gmt.clib.Session.create_data` first. Use ``|GMT_VIA_MATRIX'`` in the family. Not at all numpy dtypes are supported, only: float64, float32, int64, @@ -754,7 +754,7 @@ def put_matrix(self, dataset, matrix, pad=0): ---------- dataset : :py:class:`ctypes.c_void_p` The ctypes void pointer to a ``GMT_Dataset``. Create it with - :meth:`~gmt.clib.LibGMT.create_data`. + :meth:`~gmt.clib.Session.create_data`. matrix : numpy 2d-array The array that will be attached to the dataset. Must be a 2d C contiguous array. @@ -794,7 +794,7 @@ def write_data(self, family, geometry, mode, wesn, output, data): Write a GMT data container to a file. The data container should be created by - :meth:`~gmt.clib.LibGMT.create_data`. + :meth:`~gmt.clib.Session.create_data`. Wraps ``GMT_Write_Data`` but only allows writing to a file. So the ``method`` argument is omitted. @@ -820,7 +820,7 @@ def write_data(self, family, geometry, mode, wesn, output, data): The output file name. data : :py:class:`ctypes.c_void_p` Pointer to the data container created by - :meth:`~gmt.clib.LibGMT.create_data`. + :meth:`~gmt.clib.Session.create_data`. Raises ------ @@ -868,7 +868,7 @@ def open_virtual_file(self, family, geometry, direction, data): GMT uses a virtual file scheme to pass in data to API modules. Use it to pass in your GMT data structure (created using - :meth:`~gmt.clib.LibGMT.create_data`) to a module that expects an input + :meth:`~gmt.clib.Session.create_data`) to a module that expects an input or output file. Use in a ``with`` block. Will automatically close the virtual file when @@ -902,7 +902,7 @@ def open_virtual_file(self, family, geometry, direction, data): >>> import numpy as np >>> x = np.array([0, 1, 2, 3, 4]) >>> y = np.array([5, 6, 7, 8, 9]) - >>> with LibGMT() as lib: + >>> with Session() as lib: ... family = 'GMT_IS_DATASET|GMT_VIA_VECTOR' ... geometry = 'GMT_IS_POINT' ... dataset = lib.create_data( @@ -981,9 +981,9 @@ def vectors_to_vfile(self, *vectors): virtual file upon exit of the ``with`` block. Use this instead of creating GMT Datasets and Virtual Files by hand - with :meth:`~gmt.clib.LibGMT.create_data`, - :meth:`~gmt.clib.LibGMT.put_vector`, and - :meth:`~gmt.clib.LibGMT.open_virtual_file` + with :meth:`~gmt.clib.Session.create_data`, + :meth:`~gmt.clib.Session.put_vector`, and + :meth:`~gmt.clib.Session.open_virtual_file` The virtual file will contain the arrays as ``GMT Vector`` structures. @@ -1012,7 +1012,7 @@ def vectors_to_vfile(self, *vectors): >>> x = [1, 2, 3] >>> y = np.array([4, 5, 6]) >>> z = pd.Series([7, 8, 9]) - >>> with LibGMT() as lib: + >>> with Session() as lib: ... with lib.vectors_to_vfile(x, y, z) as vfile: ... # Send the output to a file so that we can read it ... with GMTTempFile() as ofile: @@ -1065,15 +1065,15 @@ def matrix_to_vfile(self, matrix): than just the data matrix. This creates a Dataset (table). Use this instead of creating GMT Datasets and Virtual Files by hand - with :meth:`~gmt.clib.LibGMT.create_data`, - :meth:`~gmt.clib.LibGMT.put_matrix`, and - :meth:`~gmt.clib.LibGMT.open_virtual_file` + with :meth:`~gmt.clib.Session.create_data`, + :meth:`~gmt.clib.Session.put_matrix`, and + :meth:`~gmt.clib.Session.open_virtual_file` The matrix must be C contiguous in memory. If it is not (e.g., it is a slice of a larger array), the array will be copied to make sure it is. It might be more efficient than using - :meth:`~gmt.clib.LibGMT.vectors_to_vfile` if your data are columns of a + :meth:`~gmt.clib.Session.vectors_to_vfile` if your data are columns of a 2D array. In these cases, ``vectors_to_vfile`` will have to duplicate the memory of your array in order for columns to be C contiguous. @@ -1099,7 +1099,7 @@ def matrix_to_vfile(self, matrix): [ 3 4 5] [ 6 7 8] [ 9 10 11]] - >>> with LibGMT() as lib: + >>> with Session() as lib: ... with lib.matrix_to_vfile(data) as vfile: ... # Send the output to a file so that we can read it ... with GMTTempFile() as ofile: @@ -1146,9 +1146,9 @@ def grid_to_vfile(self, grid): The virtual file will contain the grid as a ``GMT_MATRIX``. Use this instead of creating ``GMT_GRID`` and virtual files by hand - with :meth:`~gmt.clib.LibGMT.create_data`, - :meth:`~gmt.clib.LibGMT.put_matrix`, and - :meth:`~gmt.clib.LibGMT.open_virtual_file` + with :meth:`~gmt.clib.Session.create_data`, + :meth:`~gmt.clib.Session.put_matrix`, and + :meth:`~gmt.clib.Session.open_virtual_file` The grid data matrix must be C contiguous in memory. If it is not (e.g., it is a slice of a larger array), the array will be copied to @@ -1179,7 +1179,7 @@ def grid_to_vfile(self, grid): -90.0 90.0 >>> print(data.values.min(), data.values.max()) -8425.0 5551.0 - >>> with LibGMT() as lib: + >>> with Session() as lib: ... with lib.grid_to_vfile(data) as vfile: ... # Send the output to a file so that we can read it ... with GMTTempFile() as ofile: @@ -1228,7 +1228,7 @@ def extract_region(self): >>> fig = gmt.Figure() >>> fig.coast(region=[0, 10, -20, -10], projection="M6i", frame=True, ... land='black') - >>> with LibGMT() as lib: + >>> with Session() as lib: ... wesn = lib.extract_region() >>> print(', '.join(['{:.2f}'.format(x) for x in wesn])) 0.00, 10.00, -20.00, -10.00 @@ -1239,7 +1239,7 @@ def extract_region(self): >>> fig = gmt.Figure() >>> fig.coast(region='US.HI', projection="M6i", frame=True, ... land='black') - >>> with LibGMT() as lib: + >>> with Session() as lib: ... wesn = lib.extract_region() >>> print(', '.join(['{:.2f}'.format(x) for x in wesn])) -164.71, -154.81, 18.91, 23.58 @@ -1251,7 +1251,7 @@ def extract_region(self): >>> fig = gmt.Figure() >>> fig.coast(region='US.HI+r5', projection="M6i", frame=True, ... land='black') - >>> with LibGMT() as lib: + >>> with Session() as lib: ... wesn = lib.extract_region() >>> print(', '.join(['{:.2f}'.format(x) for x in wesn])) -165.00, -150.00, 15.00, 25.00 diff --git a/gmt/figure.py b/gmt/figure.py index 6255c3e7b7c..f518157af56 100644 --- a/gmt/figure.py +++ b/gmt/figure.py @@ -10,7 +10,7 @@ except ImportError: Image = None -from .clib import LibGMT +from .clib import Session from .base_plotting import BasePlotting from .exceptions import GMTError, GMTInvalidInput from .helpers import ( @@ -81,7 +81,7 @@ def _activate_figure(self): """ # Passing format '-' tells gmt.end to not produce any files. fmt = "-" - with LibGMT() as lib: + with Session() as lib: lib.call_module("figure", "{} {}".format(self._name, fmt)) def _preprocess(self, **kwargs): @@ -96,7 +96,7 @@ def _preprocess(self, **kwargs): def region(self): "The geographic WESN bounding box for the current figure." self._activate_figure() - with LibGMT() as lib: + with Session() as lib: wesn = lib.extract_region() return wesn @@ -157,7 +157,7 @@ def psconvert(self, **kwargs): # Default cropping the figure to True if "A" not in kwargs: kwargs["A"] = "" - with LibGMT() as lib: + with Session() as lib: lib.call_module("psconvert", build_arg_string(kwargs)) def savefig( diff --git a/gmt/modules.py b/gmt/modules.py index c65ce460948..8db4a27886b 100644 --- a/gmt/modules.py +++ b/gmt/modules.py @@ -1,7 +1,7 @@ """ Non-plot GMT modules. """ -from .clib import LibGMT +from .clib import Session from .helpers import ( build_arg_string, fmt_docstring, @@ -35,7 +35,7 @@ def grdinfo(grid, **kwargs): """ kind = data_kind(grid, None, None) with GMTTempFile() as outfile: - with LibGMT() as lib: + with Session() as lib: if kind == "file": file_context = dummy_context(grid) elif kind == "grid": @@ -88,7 +88,7 @@ def info(fname, **kwargs): with GMTTempFile() as tmpfile: arg_str = " ".join([fname, build_arg_string(kwargs), "->" + tmpfile.name]) - with LibGMT() as lib: + with Session() as lib: lib.call_module("info", arg_str) return tmpfile.read() @@ -136,7 +136,7 @@ def which(fname, **kwargs): """ with GMTTempFile() as tmpfile: arg_str = " ".join([fname, build_arg_string(kwargs), "->" + tmpfile.name]) - with LibGMT() as lib: + with Session() as lib: lib.call_module("which", arg_str) path = tmpfile.read().strip() if not path: diff --git a/gmt/session_management.py b/gmt/session_management.py index 02e21cef402..c380310452f 100644 --- a/gmt/session_management.py +++ b/gmt/session_management.py @@ -1,7 +1,7 @@ """ Modern mode session management modules. """ -from .clib import LibGMT +from .clib import Session def begin(): @@ -14,7 +14,7 @@ def begin(): """ prefix = "gmt-python-session" - with LibGMT() as lib: + with Session() as lib: lib.call_module("begin", prefix) @@ -28,5 +28,5 @@ def end(): ``gmt.begin``), and bring the figures to the working directory. """ - with LibGMT() as lib: + with Session() as lib: lib.call_module("end", "") diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index b1094e0fd52..c1711f504a6 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -12,7 +12,7 @@ import xarray as xr from packaging.version import Version -from ..clib.core import LibGMT +from ..clib.session import Session from ..clib.utils import ( clib_extension, load_libgmt, @@ -87,7 +87,7 @@ def test_load_libgmt_fail(): def test_get_clib_path(): "Test that the correct path is found when setting GMT_LIBRARY_PATH." # Get the real path to the library first - with LibGMT() as lib: + with Session() as lib: libpath = lib.info["library path"] libdir = os.path.dirname(libpath) # Assign it to the environment variable but keep a backup value to restore @@ -120,7 +120,7 @@ def test_clib_extension(): def test_constant(): "Test that I can get correct constants from the C lib" - lib = LibGMT() + lib = Session() assert lib.get_constant("GMT_SESSION_EXTERNAL") != -99999 assert lib.get_constant("GMT_MODULE_CMD") != -99999 assert lib.get_constant("GMT_PAD_DEFAULT") != -99999 @@ -131,7 +131,7 @@ def test_constant(): def test_create_destroy_session(): "Test that create and destroy session are called without errors" - lib = LibGMT() + lib = Session() session1 = lib.create_session(session_name="test_session1") assert session1 is not None session2 = lib.create_session(session_name="test_session2") @@ -143,7 +143,7 @@ def test_create_destroy_session(): def test_create_session_fails(): "Check that an exception is raised if the session pointer is None" - lib = LibGMT() + lib = Session() with mock(lib, "GMT_Create_Session", returns=None): with pytest.raises(GMTCLibError): lib.create_session("test-session-name") @@ -151,7 +151,7 @@ def test_create_session_fails(): def test_destroy_session_fails(): "Fail to destroy session when given bad input" - lib = LibGMT() + lib = Session() with pytest.raises(GMTCLibError): lib.destroy_session(None) @@ -160,7 +160,7 @@ def test_call_module(): "Run a command to see if call_module works" data_fname = os.path.join(TEST_DATA_DIR, "points.txt") out_fname = "test_call_module.txt" - with LibGMT() as lib: + with Session() as lib: with GMTTempFile() as out_fname: lib.call_module("info", "{} -C ->{}".format(data_fname, out_fname.name)) assert os.path.exists(out_fname.name) @@ -170,21 +170,21 @@ def test_call_module(): def test_call_module_invalid_arguments(): "Fails for invalid module arguments" - with LibGMT() as lib: + with Session() as lib: with pytest.raises(GMTCLibError): lib.call_module("info", "bogus-data.bla") def test_call_module_invalid_name(): "Fails when given bad input" - with LibGMT() as lib: + with Session() as lib: with pytest.raises(GMTCLibError): lib.call_module("meh", "") def test_call_module_error_message(): "Check is the GMT error message was captured." - with LibGMT() as lib: + with Session() as lib: try: lib.call_module("info", "bogus-data.bla") except GMTCLibError as error: @@ -199,8 +199,8 @@ def test_call_module_error_message(): def test_method_no_session(): "Fails when not in a session" - # Create an instance of LibGMT without "with" so no session is created. - lib = LibGMT() + # Create an instance of Session without "with" so no session is created. + lib = Session() with pytest.raises(GMTCLibNoSessionError): lib.call_module("gmtdefaults", "") with pytest.raises(GMTCLibNoSessionError): @@ -209,7 +209,7 @@ def test_method_no_session(): def test_parse_constant_single(): "Parsing a single family argument correctly." - lib = LibGMT() + lib = Session() for family in lib.data_families: parsed = lib._parse_constant(family, valid=lib.data_families) assert parsed == lib.get_constant(family) @@ -217,7 +217,7 @@ def test_parse_constant_single(): def test_parse_constant_composite(): "Parsing a composite constant argument (separated by |) correctly." - lib = LibGMT() + lib = Session() test_cases = ( (family, via) for family in lib.data_families for via in lib.data_vias ) @@ -232,7 +232,7 @@ def test_parse_constant_composite(): def test_parse_constant_fails(): "Check if the function fails when given bad input" - lib = LibGMT() + lib = Session() test_cases = [ "SOME_random_STRING", "GMT_IS_DATASET|GMT_VIA_MATRIX|GMT_VIA_VECTOR", @@ -264,7 +264,7 @@ def test_parse_constant_fails(): def test_create_data_dataset(): "Run the function to make sure it doesn't fail badly." - with LibGMT() as lib: + with Session() as lib: # Dataset from vectors data_vector = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", @@ -284,7 +284,7 @@ def test_create_data_dataset(): def test_create_data_grid_dim(): "Create a grid ignoring range and inc." - with LibGMT() as lib: + with Session() as lib: # Grids from matrices using dim lib.create_data( family="GMT_IS_GRID|GMT_VIA_MATRIX", @@ -296,7 +296,7 @@ def test_create_data_grid_dim(): def test_create_data_grid_range(): "Create a grid specifying range and inc instead of dim." - with LibGMT() as lib: + with Session() as lib: # Grids from matrices using range and int lib.create_data( family="GMT_IS_GRID|GMT_VIA_MATRIX", @@ -311,7 +311,7 @@ def test_create_data_fails(): "Check that create_data raises exceptions for invalid input and output" # Passing in invalid mode with pytest.raises(GMTInvalidInput): - with LibGMT() as lib: + with Session() as lib: lib.create_data( family="GMT_IS_DATASET", geometry="GMT_IS_SURFACE", @@ -322,7 +322,7 @@ def test_create_data_fails(): ) # Passing in invalid geometry with pytest.raises(GMTInvalidInput): - with LibGMT() as lib: + with Session() as lib: lib.create_data( family="GMT_IS_GRID", geometry="Not_a_valid_geometry", @@ -334,7 +334,7 @@ def test_create_data_fails(): # If the data pointer returned is None (NULL pointer) with pytest.raises(GMTCLibError): - with LibGMT() as lib: + with Session() as lib: with mock(lib, "GMT_Create_Data", returns=None): lib.create_data( family="GMT_IS_DATASET", @@ -348,7 +348,7 @@ def test_put_vector(): "Check that assigning a numpy array to a dataset works" dtypes = "float32 float64 int32 int64 uint32 uint64".split() for dtype in dtypes: - with LibGMT() as lib: + with Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -382,7 +382,7 @@ def test_put_vector(): def test_put_vector_invalid_dtype(): "Check that it fails with an exception for invalid data types" - with LibGMT() as lib: + with Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -396,7 +396,7 @@ def test_put_vector_invalid_dtype(): def test_put_vector_wrong_column(): "Check that it fails with an exception when giving an invalid column" - with LibGMT() as lib: + with Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -410,7 +410,7 @@ def test_put_vector_wrong_column(): def test_put_vector_2d_fails(): "Check that it fails with an exception for multidimensional arrays" - with LibGMT() as lib: + with Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -427,7 +427,7 @@ def test_put_matrix(): dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (3, 4) for dtype in dtypes: - with LibGMT() as lib: + with Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_MATRIX", geometry="GMT_IS_POINT", @@ -458,7 +458,7 @@ def test_put_matrix_fails(): # It's hard to make put_matrix fail on the C API level because of all the # checks on input arguments. Mock the C API function just to make sure it # works. - with LibGMT() as lib: + with Session() as lib: with mock(lib, "GMT_Put_Matrix", returns=1): with pytest.raises(GMTCLibError): lib.put_matrix(dataset=None, matrix=np.empty((10, 2)), pad=0) @@ -471,7 +471,7 @@ def test_put_matrix_grid(): inc = [1, 1] shape = ((wesn[3] - wesn[2]) // inc[1] + 1, (wesn[1] - wesn[0]) // inc[0] + 1) for dtype in dtypes: - with LibGMT() as lib: + with Session() as lib: grid = lib.create_data( family="GMT_IS_GRID|GMT_VIA_MATRIX", geometry="GMT_IS_SURFACE", @@ -502,7 +502,7 @@ def test_virtual_file(): dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (5, 3) for dtype in dtypes: - with LibGMT() as lib: + with Session() as lib: family = "GMT_IS_DATASET|GMT_VIA_MATRIX" geometry = "GMT_IS_POINT" dataset = lib.create_data( @@ -536,7 +536,7 @@ def test_virtual_file_fails(): # Mock Open_VirtualFile to test the status check when entering the context. # If the exception is raised, the code won't get to the closing of the # virtual file. - with LibGMT() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): + with Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): with pytest.raises(GMTCLibError): with lib.open_virtual_file(*vfargs): print("Should not get to this code") @@ -544,7 +544,7 @@ def test_virtual_file_fails(): # Test the status check when closing the virtual file # Mock the opening to return 0 (success) so that we don't open a file that # we won't close later. - with LibGMT() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( + with Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( lib, "GMT_Close_VirtualFile", returns=1 ): with pytest.raises(GMTCLibError): @@ -555,7 +555,7 @@ def test_virtual_file_fails(): def test_virtual_file_bad_direction(): "Test passing an invalid direction argument" - with LibGMT() as lib: + with Session() as lib: vfargs = ( "GMT_IS_DATASET|GMT_VIA_MATRIX", "GMT_IS_POINT", @@ -575,7 +575,7 @@ def test_vectors_to_vfile(): x = np.arange(size, dtype=dtype) y = np.arange(size, size * 2, 1, dtype=dtype) z = np.arange(size * 2, size * 3, 1, dtype=dtype) - with LibGMT() as lib: + with Session() as lib: with lib.vectors_to_vfile(x, y, z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -593,7 +593,7 @@ def test_vectors_to_vfile_transpose(): shape = (7, 5) for dtype in dtypes: data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with LibGMT() as lib: + with Session() as lib: with lib.vectors_to_vfile(*data.T) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} -C ->{}".format(vfile, outfile.name)) @@ -609,7 +609,7 @@ def test_vectors_to_vfile_diff_size(): "Test the function fails for arrays of different sizes" x = np.arange(5) y = np.arange(6) - with LibGMT() as lib: + with Session() as lib: with pytest.raises(GMTInvalidInput): with lib.vectors_to_vfile(x, y): print("This should have failed") @@ -621,7 +621,7 @@ def test_matrix_to_vfile(): shape = (7, 5) for dtype in dtypes: data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with LibGMT() as lib: + with Session() as lib: with lib.matrix_to_vfile(data) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -642,7 +642,7 @@ def test_matrix_to_vfile_slice(): rows = 5 cols = 3 data = full_data[:rows, :cols] - with LibGMT() as lib: + with Session() as lib: with lib.matrix_to_vfile(data) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -666,7 +666,7 @@ def test_vectors_to_vfile_pandas(): z=np.arange(size * 2, size * 3, 1, dtype=dtype), ) ) - with LibGMT() as lib: + with Session() as lib: with lib.vectors_to_vfile(data.x, data.y, data.z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -687,7 +687,7 @@ def test_vectors_to_vfile_arraylike(): x = list(range(0, size, 1)) y = tuple(range(size, size * 2, 1)) z = range(size * 2, size * 3, 1) - with LibGMT() as lib: + with Session() as lib: with lib.vectors_to_vfile(x, y, z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -703,7 +703,7 @@ def test_extract_region_fails(): "Check that extract region fails if nothing has been plotted." Figure() with pytest.raises(GMTCLibError): - with LibGMT() as lib: + with Session() as lib: lib.extract_region() @@ -720,16 +720,16 @@ def test_extract_region_two_figures(): # Activate the first figure and extract the region from it # Use in a different session to avoid any memory problems. - with LibGMT() as lib: + with Session() as lib: lib.call_module("figure", "{} -".format(fig1._name)) - with LibGMT() as lib: + with Session() as lib: wesn1 = lib.extract_region() npt.assert_allclose(wesn1, region1) # Now try it with the second one - with LibGMT() as lib: + with Session() as lib: lib.call_module("figure", "{} -".format(fig2._name)) - with LibGMT() as lib: + with Session() as lib: wesn2 = lib.extract_region() npt.assert_allclose(wesn2, np.array([-165., -150., 15., 25.])) @@ -740,7 +740,7 @@ def test_write_data_fails(): # Fault. Can't test this if by giving a bad file name because if # output=='', GMT will just write to stdout and spaces are valid file # names. Use a mock instead just to exercise this part of the code. - with LibGMT() as lib: + with Session() as lib: with mock(lib, "GMT_Write_Data", returns=1): with pytest.raises(GMTCLibError): lib.write_data( @@ -777,7 +777,7 @@ def test_dataarray_to_matrix_inc_fails(): def test_get_default(): "Make sure get_default works without crashing and gives reasonable results" - with LibGMT() as lib: + with Session() as lib: assert lib.get_default("API_GRID_LAYOUT") in ["rows", "columns"] assert int(lib.get_default("API_CORES")) >= 1 assert Version(lib.get_default("API_VERSION")) >= Version("6.0.0") @@ -785,16 +785,16 @@ def test_get_default(): def test_get_default_fails(): "Make sure get_default raises an exception for invalid names" - with LibGMT() as lib: + with Session() as lib: with pytest.raises(GMTCLibError): lib.get_default("NOT_A_VALID_NAME") def test_info_dict(): - "Make sure the LibGMT.info dict is working." + "Make sure the Session.info dict is working." # Check if there are no errors or segfaults from getting all of the # properties. - with LibGMT() as lib: + with Session() as lib: assert lib.info # Mock GMT_Get_Default to return always the same string @@ -803,7 +803,7 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument value.value = b"bla" return 0 - with LibGMT() as lib: + with Session() as lib: with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): info = lib.info # Check for an empty dictionary @@ -813,7 +813,7 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument def test_fails_for_wrong_version(): - "Make sure the LibGMT raises an exception if GMT is too old" + "Make sure the Session raises an exception if GMT is too old" # Mock GMT_Get_Default to return an old version def mock_defaults(api, name, value): # pylint: disable=unused-argument @@ -824,7 +824,7 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument value.value = b"bla" return 0 - lib = LibGMT() + lib = Session() with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): with pytest.raises(GMTVersionError): with lib: diff --git a/gmt/tests/test_session_management.py b/gmt/tests/test_session_management.py index b421be8a7c5..34fa8ffa5bd 100644 --- a/gmt/tests/test_session_management.py +++ b/gmt/tests/test_session_management.py @@ -4,7 +4,7 @@ import os from ..session_management import begin, end -from ..clib import LibGMT +from ..clib import Session def test_begin_end(): @@ -14,7 +14,7 @@ def test_begin_end(): """ end() # Kill the global session begin() - with LibGMT() as lib: + with Session() as lib: lib.call_module("psbasemap", "-R10/70/-3/8 -JX4i/3i -Ba") end() begin() # Restart the global session From fc7ef71ca82b583f4a0e1a7da0f5515d0736c5eb Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Thu, 5 Jul 2018 12:31:20 -1000 Subject: [PATCH 2/9] Move constants to the module namespace --- gmt/clib/session.py | 92 ++++++++++++++--------------- gmt/tests/test_clib.py | 129 +++++++++++++++++++---------------------- 2 files changed, 102 insertions(+), 119 deletions(-) diff --git a/gmt/clib/session.py b/gmt/clib/session.py index 833e47d1530..884280947c2 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -22,6 +22,38 @@ as_c_contiguous, ) +FAMILIES = [ + "GMT_IS_DATASET", + "GMT_IS_GRID", + "GMT_IS_PALETTE", + "GMT_IS_MATRIX", + "GMT_IS_VECTOR", +] + +VIAS = ["GMT_VIA_MATRIX", "GMT_VIA_VECTOR"] + +GEOMETRIES = [ + "GMT_IS_NONE", + "GMT_IS_POINT", + "GMT_IS_LINE", + "GMT_IS_POLYGON", + "GMT_IS_PLP", + "GMT_IS_SURFACE", +] + +MODES = ["GMT_CONTAINER_ONLY", "GMT_OUTPUT"] + +REGISTRATIONS = ["GMT_GRID_PIXEL_REG", "GMT_GRID_NODE_REG"] + +DTYPES = { + "float64": "GMT_DOUBLE", + "float32": "GMT_FLOAT", + "int64": "GMT_LONG", + "int32": "GMT_INT", + "uint64": "GMT_ULONG", + "uint32": "GMT_UINT", +} + class Session: # pylint: disable=too-many-instance-attributes """ @@ -66,42 +98,9 @@ class Session: # pylint: disable=too-many-instance-attributes """ - data_families = [ - "GMT_IS_DATASET", - "GMT_IS_GRID", - "GMT_IS_PALETTE", - "GMT_IS_MATRIX", - "GMT_IS_VECTOR", - ] - - data_vias = ["GMT_VIA_MATRIX", "GMT_VIA_VECTOR"] - - data_geometries = [ - "GMT_IS_NONE", - "GMT_IS_POINT", - "GMT_IS_LINE", - "GMT_IS_POLYGON", - "GMT_IS_PLP", - "GMT_IS_SURFACE", - ] - - data_modes = ["GMT_CONTAINER_ONLY", "GMT_OUTPUT"] - - grid_registrations = ["GMT_GRID_PIXEL_REG", "GMT_GRID_NODE_REG"] - # The minimum version of GMT required required_version = "6.0.0" - # Map numpy dtypes to GMT types - _dtypes = { - "float64": "GMT_DOUBLE", - "float32": "GMT_FLOAT", - "int64": "GMT_LONG", - "int32": "GMT_INT", - "uint64": "GMT_ULONG", - "uint32": "GMT_UINT", - } - @property def current_session(self): """ @@ -506,16 +505,13 @@ def create_data(self, family, geometry, mode, **kwargs): restype=ctypes.c_void_p, ) - family_int = self._parse_constant( - family, valid=self.data_families, valid_modifiers=self.data_vias - ) + family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) mode_int = self._parse_constant( - mode, valid=self.data_modes, valid_modifiers=["GMT_GRID_IS_GEO"] + mode, valid=MODES, valid_modifiers=["GMT_GRID_IS_GEO"] ) - geometry_int = self._parse_constant(geometry, valid=self.data_geometries) + geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) registration_int = self._parse_constant( - kwargs.get("registration", "GMT_GRID_NODE_REG"), - valid=self.grid_registrations, + kwargs.get("registration", "GMT_GRID_NODE_REG"), valid=REGISTRATIONS ) # Convert dim, ranges, and inc to ctypes arrays if given (will be None @@ -658,7 +654,7 @@ def _check_dtype_and_dim(self, array, ndim): True """ - if array.dtype.name not in self._dtypes: + if array.dtype.name not in DTYPES: raise GMTInvalidInput( "Unsupported numpy data type '{}'.".format(array.dtype.name) ) @@ -666,7 +662,7 @@ def _check_dtype_and_dim(self, array, ndim): raise GMTInvalidInput( "Expected a numpy 1d array, got {}d.".format(array.ndim) ) - return self.get_constant(self._dtypes[array.dtype.name]) + return self.get_constant(DTYPES[array.dtype.name]) def put_vector(self, dataset, column, vector): """ @@ -844,10 +840,8 @@ def write_data(self, family, geometry, mode, wesn, output, data): restype=ctypes.c_int, ) - family_int = self._parse_constant( - family, valid=self.data_families, valid_modifiers=self.data_vias - ) - geometry_int = self._parse_constant(geometry, valid=self.data_geometries) + family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) + geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) status = c_write_data( self.current_session, family_int, @@ -943,10 +937,8 @@ def open_virtual_file(self, family, geometry, direction, data): restype=ctypes.c_int, ) - family_int = self._parse_constant( - family, valid=self.data_families, valid_modifiers=self.data_vias - ) - geometry_int = self._parse_constant(geometry, valid=self.data_geometries) + family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) + geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) direction_int = self._parse_constant( direction, valid=["GMT_IN", "GMT_OUT"], diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index c1711f504a6..c01da250eb7 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -12,7 +12,8 @@ import xarray as xr from packaging.version import Version -from ..clib.session import Session +from .. import clib +from ..clib.session import FAMILIES, VIAS from ..clib.utils import ( clib_extension, load_libgmt, @@ -87,7 +88,7 @@ def test_load_libgmt_fail(): def test_get_clib_path(): "Test that the correct path is found when setting GMT_LIBRARY_PATH." # Get the real path to the library first - with Session() as lib: + with clib.Session() as lib: libpath = lib.info["library path"] libdir = os.path.dirname(libpath) # Assign it to the environment variable but keep a backup value to restore @@ -120,7 +121,7 @@ def test_clib_extension(): def test_constant(): "Test that I can get correct constants from the C lib" - lib = Session() + lib = clib.Session() assert lib.get_constant("GMT_SESSION_EXTERNAL") != -99999 assert lib.get_constant("GMT_MODULE_CMD") != -99999 assert lib.get_constant("GMT_PAD_DEFAULT") != -99999 @@ -131,7 +132,7 @@ def test_constant(): def test_create_destroy_session(): "Test that create and destroy session are called without errors" - lib = Session() + lib = clib.Session() session1 = lib.create_session(session_name="test_session1") assert session1 is not None session2 = lib.create_session(session_name="test_session2") @@ -143,7 +144,7 @@ def test_create_destroy_session(): def test_create_session_fails(): "Check that an exception is raised if the session pointer is None" - lib = Session() + lib = clib.Session() with mock(lib, "GMT_Create_Session", returns=None): with pytest.raises(GMTCLibError): lib.create_session("test-session-name") @@ -151,7 +152,7 @@ def test_create_session_fails(): def test_destroy_session_fails(): "Fail to destroy session when given bad input" - lib = Session() + lib = clib.Session() with pytest.raises(GMTCLibError): lib.destroy_session(None) @@ -160,7 +161,7 @@ def test_call_module(): "Run a command to see if call_module works" data_fname = os.path.join(TEST_DATA_DIR, "points.txt") out_fname = "test_call_module.txt" - with Session() as lib: + with clib.Session() as lib: with GMTTempFile() as out_fname: lib.call_module("info", "{} -C ->{}".format(data_fname, out_fname.name)) assert os.path.exists(out_fname.name) @@ -170,21 +171,21 @@ def test_call_module(): def test_call_module_invalid_arguments(): "Fails for invalid module arguments" - with Session() as lib: + with clib.Session() as lib: with pytest.raises(GMTCLibError): lib.call_module("info", "bogus-data.bla") def test_call_module_invalid_name(): "Fails when given bad input" - with Session() as lib: + with clib.Session() as lib: with pytest.raises(GMTCLibError): lib.call_module("meh", "") def test_call_module_error_message(): "Check is the GMT error message was captured." - with Session() as lib: + with clib.Session() as lib: try: lib.call_module("info", "bogus-data.bla") except GMTCLibError as error: @@ -199,8 +200,8 @@ def test_call_module_error_message(): def test_method_no_session(): "Fails when not in a session" - # Create an instance of Session without "with" so no session is created. - lib = Session() + # Create an instance of clib.Session without "with" so no session is created. + lib = clib.Session() with pytest.raises(GMTCLibNoSessionError): lib.call_module("gmtdefaults", "") with pytest.raises(GMTCLibNoSessionError): @@ -209,30 +210,26 @@ def test_method_no_session(): def test_parse_constant_single(): "Parsing a single family argument correctly." - lib = Session() - for family in lib.data_families: - parsed = lib._parse_constant(family, valid=lib.data_families) + lib = clib.Session() + for family in FAMILIES: + parsed = lib._parse_constant(family, valid=FAMILIES) assert parsed == lib.get_constant(family) def test_parse_constant_composite(): "Parsing a composite constant argument (separated by |) correctly." - lib = Session() - test_cases = ( - (family, via) for family in lib.data_families for via in lib.data_vias - ) + lib = clib.Session() + test_cases = ((family, via) for family in FAMILIES for via in VIAS) for family, via in test_cases: composite = "|".join([family, via]) expected = lib.get_constant(family) + lib.get_constant(via) - parsed = lib._parse_constant( - composite, valid=lib.data_families, valid_modifiers=lib.data_vias - ) + parsed = lib._parse_constant(composite, valid=FAMILIES, valid_modifiers=VIAS) assert parsed == expected def test_parse_constant_fails(): "Check if the function fails when given bad input" - lib = Session() + lib = clib.Session() test_cases = [ "SOME_random_STRING", "GMT_IS_DATASET|GMT_VIA_MATRIX|GMT_VIA_VECTOR", @@ -242,29 +239,23 @@ def test_parse_constant_fails(): ] for test_case in test_cases: with pytest.raises(GMTInvalidInput): - lib._parse_constant( - test_case, valid=lib.data_families, valid_modifiers=lib.data_vias - ) + lib._parse_constant(test_case, valid=FAMILIES, valid_modifiers=VIAS) # Should also fail if not given valid modifiers but is using them anyway. # This should work... lib._parse_constant( - "GMT_IS_DATASET|GMT_VIA_MATRIX", - valid=lib.data_families, - valid_modifiers=lib.data_vias, + "GMT_IS_DATASET|GMT_VIA_MATRIX", valid=FAMILIES, valid_modifiers=VIAS ) # But this shouldn't. with pytest.raises(GMTInvalidInput): lib._parse_constant( - "GMT_IS_DATASET|GMT_VIA_MATRIX", - valid=lib.data_families, - valid_modifiers=None, + "GMT_IS_DATASET|GMT_VIA_MATRIX", valid=FAMILIES, valid_modifiers=None ) def test_create_data_dataset(): "Run the function to make sure it doesn't fail badly." - with Session() as lib: + with clib.Session() as lib: # Dataset from vectors data_vector = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", @@ -284,7 +275,7 @@ def test_create_data_dataset(): def test_create_data_grid_dim(): "Create a grid ignoring range and inc." - with Session() as lib: + with clib.Session() as lib: # Grids from matrices using dim lib.create_data( family="GMT_IS_GRID|GMT_VIA_MATRIX", @@ -296,7 +287,7 @@ def test_create_data_grid_dim(): def test_create_data_grid_range(): "Create a grid specifying range and inc instead of dim." - with Session() as lib: + with clib.Session() as lib: # Grids from matrices using range and int lib.create_data( family="GMT_IS_GRID|GMT_VIA_MATRIX", @@ -311,7 +302,7 @@ def test_create_data_fails(): "Check that create_data raises exceptions for invalid input and output" # Passing in invalid mode with pytest.raises(GMTInvalidInput): - with Session() as lib: + with clib.Session() as lib: lib.create_data( family="GMT_IS_DATASET", geometry="GMT_IS_SURFACE", @@ -322,7 +313,7 @@ def test_create_data_fails(): ) # Passing in invalid geometry with pytest.raises(GMTInvalidInput): - with Session() as lib: + with clib.Session() as lib: lib.create_data( family="GMT_IS_GRID", geometry="Not_a_valid_geometry", @@ -334,7 +325,7 @@ def test_create_data_fails(): # If the data pointer returned is None (NULL pointer) with pytest.raises(GMTCLibError): - with Session() as lib: + with clib.Session() as lib: with mock(lib, "GMT_Create_Data", returns=None): lib.create_data( family="GMT_IS_DATASET", @@ -348,7 +339,7 @@ def test_put_vector(): "Check that assigning a numpy array to a dataset works" dtypes = "float32 float64 int32 int64 uint32 uint64".split() for dtype in dtypes: - with Session() as lib: + with clib.Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -382,7 +373,7 @@ def test_put_vector(): def test_put_vector_invalid_dtype(): "Check that it fails with an exception for invalid data types" - with Session() as lib: + with clib.Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -396,7 +387,7 @@ def test_put_vector_invalid_dtype(): def test_put_vector_wrong_column(): "Check that it fails with an exception when giving an invalid column" - with Session() as lib: + with clib.Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -410,7 +401,7 @@ def test_put_vector_wrong_column(): def test_put_vector_2d_fails(): "Check that it fails with an exception for multidimensional arrays" - with Session() as lib: + with clib.Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_VECTOR", geometry="GMT_IS_POINT", @@ -427,7 +418,7 @@ def test_put_matrix(): dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (3, 4) for dtype in dtypes: - with Session() as lib: + with clib.Session() as lib: dataset = lib.create_data( family="GMT_IS_DATASET|GMT_VIA_MATRIX", geometry="GMT_IS_POINT", @@ -458,7 +449,7 @@ def test_put_matrix_fails(): # It's hard to make put_matrix fail on the C API level because of all the # checks on input arguments. Mock the C API function just to make sure it # works. - with Session() as lib: + with clib.Session() as lib: with mock(lib, "GMT_Put_Matrix", returns=1): with pytest.raises(GMTCLibError): lib.put_matrix(dataset=None, matrix=np.empty((10, 2)), pad=0) @@ -471,7 +462,7 @@ def test_put_matrix_grid(): inc = [1, 1] shape = ((wesn[3] - wesn[2]) // inc[1] + 1, (wesn[1] - wesn[0]) // inc[0] + 1) for dtype in dtypes: - with Session() as lib: + with clib.Session() as lib: grid = lib.create_data( family="GMT_IS_GRID|GMT_VIA_MATRIX", geometry="GMT_IS_SURFACE", @@ -502,7 +493,7 @@ def test_virtual_file(): dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (5, 3) for dtype in dtypes: - with Session() as lib: + with clib.Session() as lib: family = "GMT_IS_DATASET|GMT_VIA_MATRIX" geometry = "GMT_IS_POINT" dataset = lib.create_data( @@ -536,7 +527,7 @@ def test_virtual_file_fails(): # Mock Open_VirtualFile to test the status check when entering the context. # If the exception is raised, the code won't get to the closing of the # virtual file. - with Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): + with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): with pytest.raises(GMTCLibError): with lib.open_virtual_file(*vfargs): print("Should not get to this code") @@ -544,7 +535,7 @@ def test_virtual_file_fails(): # Test the status check when closing the virtual file # Mock the opening to return 0 (success) so that we don't open a file that # we won't close later. - with Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( + with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( lib, "GMT_Close_VirtualFile", returns=1 ): with pytest.raises(GMTCLibError): @@ -555,7 +546,7 @@ def test_virtual_file_fails(): def test_virtual_file_bad_direction(): "Test passing an invalid direction argument" - with Session() as lib: + with clib.Session() as lib: vfargs = ( "GMT_IS_DATASET|GMT_VIA_MATRIX", "GMT_IS_POINT", @@ -575,7 +566,7 @@ def test_vectors_to_vfile(): x = np.arange(size, dtype=dtype) y = np.arange(size, size * 2, 1, dtype=dtype) z = np.arange(size * 2, size * 3, 1, dtype=dtype) - with Session() as lib: + with clib.Session() as lib: with lib.vectors_to_vfile(x, y, z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -593,7 +584,7 @@ def test_vectors_to_vfile_transpose(): shape = (7, 5) for dtype in dtypes: data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with Session() as lib: + with clib.Session() as lib: with lib.vectors_to_vfile(*data.T) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} -C ->{}".format(vfile, outfile.name)) @@ -609,7 +600,7 @@ def test_vectors_to_vfile_diff_size(): "Test the function fails for arrays of different sizes" x = np.arange(5) y = np.arange(6) - with Session() as lib: + with clib.Session() as lib: with pytest.raises(GMTInvalidInput): with lib.vectors_to_vfile(x, y): print("This should have failed") @@ -621,7 +612,7 @@ def test_matrix_to_vfile(): shape = (7, 5) for dtype in dtypes: data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with Session() as lib: + with clib.Session() as lib: with lib.matrix_to_vfile(data) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -642,7 +633,7 @@ def test_matrix_to_vfile_slice(): rows = 5 cols = 3 data = full_data[:rows, :cols] - with Session() as lib: + with clib.Session() as lib: with lib.matrix_to_vfile(data) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -666,7 +657,7 @@ def test_vectors_to_vfile_pandas(): z=np.arange(size * 2, size * 3, 1, dtype=dtype), ) ) - with Session() as lib: + with clib.Session() as lib: with lib.vectors_to_vfile(data.x, data.y, data.z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -687,7 +678,7 @@ def test_vectors_to_vfile_arraylike(): x = list(range(0, size, 1)) y = tuple(range(size, size * 2, 1)) z = range(size * 2, size * 3, 1) - with Session() as lib: + with clib.Session() as lib: with lib.vectors_to_vfile(x, y, z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) @@ -703,7 +694,7 @@ def test_extract_region_fails(): "Check that extract region fails if nothing has been plotted." Figure() with pytest.raises(GMTCLibError): - with Session() as lib: + with clib.Session() as lib: lib.extract_region() @@ -720,16 +711,16 @@ def test_extract_region_two_figures(): # Activate the first figure and extract the region from it # Use in a different session to avoid any memory problems. - with Session() as lib: + with clib.Session() as lib: lib.call_module("figure", "{} -".format(fig1._name)) - with Session() as lib: + with clib.Session() as lib: wesn1 = lib.extract_region() npt.assert_allclose(wesn1, region1) # Now try it with the second one - with Session() as lib: + with clib.Session() as lib: lib.call_module("figure", "{} -".format(fig2._name)) - with Session() as lib: + with clib.Session() as lib: wesn2 = lib.extract_region() npt.assert_allclose(wesn2, np.array([-165., -150., 15., 25.])) @@ -740,7 +731,7 @@ def test_write_data_fails(): # Fault. Can't test this if by giving a bad file name because if # output=='', GMT will just write to stdout and spaces are valid file # names. Use a mock instead just to exercise this part of the code. - with Session() as lib: + with clib.Session() as lib: with mock(lib, "GMT_Write_Data", returns=1): with pytest.raises(GMTCLibError): lib.write_data( @@ -777,7 +768,7 @@ def test_dataarray_to_matrix_inc_fails(): def test_get_default(): "Make sure get_default works without crashing and gives reasonable results" - with Session() as lib: + with clib.Session() as lib: assert lib.get_default("API_GRID_LAYOUT") in ["rows", "columns"] assert int(lib.get_default("API_CORES")) >= 1 assert Version(lib.get_default("API_VERSION")) >= Version("6.0.0") @@ -785,16 +776,16 @@ def test_get_default(): def test_get_default_fails(): "Make sure get_default raises an exception for invalid names" - with Session() as lib: + with clib.Session() as lib: with pytest.raises(GMTCLibError): lib.get_default("NOT_A_VALID_NAME") def test_info_dict(): - "Make sure the Session.info dict is working." + "Make sure the clib.Session.info dict is working." # Check if there are no errors or segfaults from getting all of the # properties. - with Session() as lib: + with clib.Session() as lib: assert lib.info # Mock GMT_Get_Default to return always the same string @@ -803,7 +794,7 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument value.value = b"bla" return 0 - with Session() as lib: + with clib.Session() as lib: with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): info = lib.info # Check for an empty dictionary @@ -813,7 +804,7 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument def test_fails_for_wrong_version(): - "Make sure the Session raises an exception if GMT is too old" + "Make sure the clib.Session raises an exception if GMT is too old" # Mock GMT_Get_Default to return an old version def mock_defaults(api, name, value): # pylint: disable=unused-argument @@ -824,7 +815,7 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument value.value = b"bla" return 0 - lib = Session() + lib = clib.Session() with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): with pytest.raises(GMTVersionError): with lib: From 9edfd2208c78471e8d21cbd9b0a606c0b321617f Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Thu, 5 Jul 2018 13:23:54 -1000 Subject: [PATCH 3/9] Update docstring of Session --- gmt/clib/session.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/gmt/clib/session.py b/gmt/clib/session.py index 884280947c2..44efce450e6 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -55,30 +55,31 @@ } -class Session: # pylint: disable=too-many-instance-attributes +class Session: """ - Load and access the GMT shared library (libgmt). + A GMT API session where most operations involving the C API happen. - Works as a context manager to create a GMT C API session and destroy it in - the end. The context manager feature eliminates the need for the - ``GMT_Create_Session`` and ``GMT_Destroy_Session`` functions. Thus, they - are not exposed in the Python API. If you need the void pointer to the GMT - session, use the ``current_session`` attribute. + Works as a context manager to create a GMT C API session and destroy it in the end + to clean up memory. - Functions of the shared library are exposed as methods of this class. Most - methods MUST be used inside the context manager 'with' block. + Functions of the shared library are exposed as methods of this class. Most methods + MUST be used with an open session (inside a ``with`` block). If creating GMT data + structures to communicate data, put that code inside the same ``with`` block as the + API calls that will use this data so that they are in the same session. - If creating GMT data structures to communicate data, put that code inside - this context manager to reuse the same session. + By default, will look for the shared library in the directory specified by the + environment variable ``GMT_LIBRARY_PATH``. If the variable is not set, will let + ctypes try to find the library. - Requires a minimum version of GMT (see ``Session.required_version``). Will - check for the version when entering the ``with`` block. A - ``GMTVersionError`` exception will be raised if the minimum version - requirements aren't met. + Requires GMT 6 (see ``Session.required_version``). Will check for the version when + entering the ``with`` block. A ``GMTVersionError`` exception will be raised if the + minimum version requirements aren't met. - By default, will look for the shared library in the directory specified by - the environment variable ``GMT_LIBRARY_PATH``. If the variable is not set, - will let ctypes try to find the library. + The ``session_pointer`` attribute holds a ctypes pointer to the currently open + session. + + The context manager feature eliminates the need for the ``GMT_Create_Session`` and + ``GMT_Destroy_Session`` functions. Thus, they are not exposed in the Python API. Raises ------ From daa5cd804a43b644f85d8eb687beed46e7751ef9 Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Fri, 6 Jul 2018 17:14:58 -1000 Subject: [PATCH 4/9] Reorganize the utility functions Separate the functions for finding libgmt. --- gmt/clib/{utils.py => conversion.py} | 140 +------------------------ gmt/clib/loading.py | 146 +++++++++++++++++++++++++++ gmt/clib/session.py | 4 +- gmt/tests/test_clib.py | 4 +- 4 files changed, 152 insertions(+), 142 deletions(-) rename gmt/clib/{utils.py => conversion.py} (67%) create mode 100644 gmt/clib/loading.py diff --git a/gmt/clib/utils.py b/gmt/clib/conversion.py similarity index 67% rename from gmt/clib/utils.py rename to gmt/clib/conversion.py index 720d73c9569..988c46b89fd 100644 --- a/gmt/clib/utils.py +++ b/gmt/clib/conversion.py @@ -1,5 +1,5 @@ """ -Miscellaneous utilities +Functions to convert data types into ctypes friendly formats. """ import os import sys @@ -242,142 +242,6 @@ def _as_array(vector): return np.asarray(vector) -def load_libgmt(env=None): - """ - Find and load ``libgmt`` as a :py:class:`ctypes.CDLL`. - - By default, will look for the shared library in the directory specified by - the environment variable ``GMT_LIBRARY_PATH``. If it's not set, will let - ctypes try to find the library. - - Parameters - ---------- - env : dict or None - A dictionary containing the environment variables. If ``None``, will - default to ``os.environ``. - - Returns - ------- - :py:class:`ctypes.CDLL` object - The loaded shared library. - - Raises - ------ - GMTCLibNotFoundError - If there was any problem loading the library (couldn't find it or - couldn't access the functions). - - """ - libpath = get_clib_path(env) - try: - libgmt = ctypes.CDLL(libpath) - check_libgmt(libgmt) - except OSError as err: - msg = "\n".join( - [ - "Couldn't find the GMT shared library '{}'.".format(libpath), - "Original error message:", - "{}".format(str(err)), - ] - ) - raise GMTCLibNotFoundError(msg) - return libgmt - - -def get_clib_path(env): - """ - Get the path to the libgmt shared library. - - Determine the file name and extension and append to the path set by - ``GMT_LIBRARY_PATH``, if any. - - Parameters - ---------- - env : dict or None - A dictionary containing the environment variables. If ``None``, will - default to ``os.environ``. - - Returns - ------- - libpath : str - The path to the libgmt shared library. - - """ - libname = ".".join(["libgmt", clib_extension()]) - if env is None: - env = os.environ - if "GMT_LIBRARY_PATH" in env: - libpath = os.path.join(env["GMT_LIBRARY_PATH"], libname) - else: - libpath = libname - return libpath - - -def clib_extension(os_name=None): - """ - Return the extension for the shared library for the current OS. - - .. warning:: - - Currently only works for macOS and Linux. - - Returns - ------- - os_name : str or None - The operating system name as given by ``sys.platform`` - (the default if None). - - Returns - ------- - ext : str - The extension ('.so', '.dylib', etc). - - """ - if os_name is None: - os_name = sys.platform - # Set the shared library extension in a platform independent way - if os_name.startswith("linux"): - lib_ext = "so" - elif os_name == "darwin": - # Darwin is macOS - lib_ext = "dylib" - else: - raise GMTOSError('Operating system "{}" not supported.'.format(sys.platform)) - return lib_ext - - -def check_libgmt(libgmt): - """ - Make sure that libgmt was loaded correctly. - - Checks if it defines some common required functions. - - Does nothing if everything is fine. Raises an exception if any of the - functions are missing. - - Parameters - ---------- - libgmt : :py:class:`ctypes.CDLL` - A shared library loaded using ctypes. - - Raises - ------ - GMTCLibError - - """ - # Check if a few of the functions we need are in the library - functions = ["Create_Session", "Get_Enum", "Call_Module", "Destroy_Session"] - for func in functions: - if not hasattr(libgmt, "GMT_" + func): - msg = " ".join( - [ - "Error loading libgmt.", - "Couldn't access function GMT_{}.".format(func), - ] - ) - raise GMTCLibError(msg) - - def kwargs_to_ctypes_array(argument, kwargs, dtype): """ Convert an iterable argument from kwargs into a ctypes array variable. @@ -403,7 +267,7 @@ def kwargs_to_ctypes_array(argument, kwargs, dtype): >>> import ctypes as ct >>> value = kwargs_to_ctypes_array('bla', {'bla': [10, 10]}, ct.c_int*2) >>> type(value) - + >>> should_be_none = kwargs_to_ctypes_array( ... 'swallow', {'bla': 1, 'foo': [20, 30]}, ct.c_int*2) >>> print(should_be_none) diff --git a/gmt/clib/loading.py b/gmt/clib/loading.py new file mode 100644 index 00000000000..47184e07f35 --- /dev/null +++ b/gmt/clib/loading.py @@ -0,0 +1,146 @@ +""" +Utility functions to load libgmt as ctypes.CDLL. + +The path to the shared library can be found automatically by ctypes or set through the +GMT_LIBRARY_PATH environment variable. +""" +import os +import sys +import ctypes + +from ..exceptions import GMTOSError, GMTCLibError, GMTCLibNotFoundError, GMTInvalidInput + + +def load_libgmt(env=None): + """ + Find and load ``libgmt`` as a :py:class:`ctypes.CDLL`. + + By default, will look for the shared library in the directory specified by + the environment variable ``GMT_LIBRARY_PATH``. If it's not set, will let + ctypes try to find the library. + + Parameters + ---------- + env : dict or None + A dictionary containing the environment variables. If ``None``, will + default to ``os.environ``. + + Returns + ------- + :py:class:`ctypes.CDLL` object + The loaded shared library. + + Raises + ------ + GMTCLibNotFoundError + If there was any problem loading the library (couldn't find it or + couldn't access the functions). + + """ + libpath = get_clib_path(env) + try: + libgmt = ctypes.CDLL(libpath) + check_libgmt(libgmt) + except OSError as err: + msg = "\n".join( + [ + "Error loading the GMT shared library '{}':".format(libpath), + "{}".format(str(err)), + ] + ) + raise GMTCLibNotFoundError(msg) + return libgmt + + +def get_clib_path(env): + """ + Get the path to the libgmt shared library. + + Determine the file name and extension and append to the path set by + ``GMT_LIBRARY_PATH``, if any. + + Parameters + ---------- + env : dict or None + A dictionary containing the environment variables. If ``None``, will + default to ``os.environ``. + + Returns + ------- + libpath : str + The path to the libgmt shared library. + + """ + libname = ".".join(["libgmt", clib_extension()]) + if env is None: + env = os.environ + if "GMT_LIBRARY_PATH" in env: + libpath = os.path.join(env["GMT_LIBRARY_PATH"], libname) + else: + libpath = libname + return libpath + + +def clib_extension(os_name=None): + """ + Return the extension for the shared library for the current OS. + + .. warning:: + + Currently only works for macOS and Linux. + + Returns + ------- + os_name : str or None + The operating system name as given by ``sys.platform`` + (the default if None). + + Returns + ------- + ext : str + The extension ('.so', '.dylib', etc). + + """ + if os_name is None: + os_name = sys.platform + # Set the shared library extension in a platform independent way + if os_name.startswith("linux"): + lib_ext = "so" + elif os_name == "darwin": + # Darwin is macOS + lib_ext = "dylib" + else: + raise GMTOSError('Operating system "{}" not supported.'.format(sys.platform)) + return lib_ext + + +def check_libgmt(libgmt): + """ + Make sure that libgmt was loaded correctly. + + Checks if it defines some common required functions. + + Does nothing if everything is fine. Raises an exception if any of the + functions are missing. + + Parameters + ---------- + libgmt : :py:class:`ctypes.CDLL` + A shared library loaded using ctypes. + + Raises + ------ + GMTCLibError + + """ + # Check if a few of the functions we need are in the library + functions = ["Create_Session", "Get_Enum", "Call_Module", "Destroy_Session"] + for func in functions: + if not hasattr(libgmt, "GMT_" + func): + msg = " ".join( + [ + "Error loading libgmt.", + "Couldn't access function GMT_{}.".format(func), + ] + ) + raise GMTCLibError(msg) diff --git a/gmt/clib/session.py b/gmt/clib/session.py index 44efce450e6..ace1521d0ba 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -14,8 +14,8 @@ GMTInvalidInput, GMTVersionError, ) -from .utils import ( - load_libgmt, +from.loading import load_libgmt +from .conversion import ( kwargs_to_ctypes_array, vectors_to_arrays, dataarray_to_matrix, diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index c01da250eb7..6c298d4692e 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -14,13 +14,13 @@ from .. import clib from ..clib.session import FAMILIES, VIAS -from ..clib.utils import ( +from ..clib.loading import ( clib_extension, load_libgmt, check_libgmt, - dataarray_to_matrix, get_clib_path, ) +from..clib.conversion import dataarray_to_matrix from ..exceptions import ( GMTCLibError, GMTOSError, From df7a8f2161a08c00502a25747e8e5fd6c197848a Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Fri, 6 Jul 2018 18:02:13 -1000 Subject: [PATCH 5/9] Clean up the handling of the session pointer --- gmt/clib/conversion.py | 6 +-- gmt/clib/loading.py | 2 +- gmt/clib/session.py | 109 ++++++++++++++++++++++------------------- gmt/tests/test_clib.py | 19 +++---- 4 files changed, 69 insertions(+), 67 deletions(-) diff --git a/gmt/clib/conversion.py b/gmt/clib/conversion.py index 988c46b89fd..08a77020513 100644 --- a/gmt/clib/conversion.py +++ b/gmt/clib/conversion.py @@ -1,14 +1,10 @@ """ Functions to convert data types into ctypes friendly formats. """ -import os -import sys -import ctypes - import numpy as np import pandas -from ..exceptions import GMTOSError, GMTCLibError, GMTCLibNotFoundError, GMTInvalidInput +from ..exceptions import GMTInvalidInput def dataarray_to_matrix(grid): diff --git a/gmt/clib/loading.py b/gmt/clib/loading.py index 47184e07f35..f1e07d8cac9 100644 --- a/gmt/clib/loading.py +++ b/gmt/clib/loading.py @@ -8,7 +8,7 @@ import sys import ctypes -from ..exceptions import GMTOSError, GMTCLibError, GMTCLibNotFoundError, GMTInvalidInput +from ..exceptions import GMTOSError, GMTCLibError, GMTCLibNotFoundError def load_libgmt(env=None): diff --git a/gmt/clib/session.py b/gmt/clib/session.py index ace1521d0ba..56ef65e12f2 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -14,7 +14,7 @@ GMTInvalidInput, GMTVersionError, ) -from.loading import load_libgmt +from .loading import load_libgmt from .conversion import ( kwargs_to_ctypes_array, vectors_to_arrays, @@ -59,21 +59,20 @@ class Session: """ A GMT API session where most operations involving the C API happen. - Works as a context manager to create a GMT C API session and destroy it in the end - to clean up memory. + Works as a context manager (for use in a ``with`` block) to create a GMT C API + session and destroy it in the end to clean up memory. Functions of the shared library are exposed as methods of this class. Most methods MUST be used with an open session (inside a ``with`` block). If creating GMT data structures to communicate data, put that code inside the same ``with`` block as the - API calls that will use this data so that they are in the same session. + API calls that will use the data. - By default, will look for the shared library in the directory specified by the - environment variable ``GMT_LIBRARY_PATH``. If the variable is not set, will let - ctypes try to find the library. + By default, will let ctypes try to find the GMT shared library (``libgmt``). If the + environment variable ``GMT_LIBRARY_PATH`` is set, will look for the shared library + in the directory specified by it. - Requires GMT 6 (see ``Session.required_version``). Will check for the version when - entering the ``with`` block. A ``GMTVersionError`` exception will be raised if the - minimum version requirements aren't met. + A ``GMTVersionError`` exception will be raised if the GMT shared library reports a + version < 6.0.0. The ``session_pointer`` attribute holds a ctypes pointer to the currently open session. @@ -84,8 +83,8 @@ class Session: Raises ------ GMTCLibNotFoundError - If there was any problem loading the library (couldn't find it or - couldn't access the functions). + If there was any problem loading the library (couldn't find it or couldn't + access the functions). GMTCLibNoSessionError If you try to call a method outside of a 'with' block. GMTVersionError @@ -94,8 +93,17 @@ class Session: Examples -------- - >>> with Session() as lib: - ... lib.call_module('figure', 'my-figure') + >>> from gmt.datasets import load_earth_relief + >>> from gmt.helpers import GMTTempFile + >>> grid = load_earth_relief() + >>> type(grid) + + >>> with Session() as ses: + ... with ses.grid_to_vfile(grid) as fin: + ... with GMTTempFile() as fout: + ... ses.call_module("grdinfo", "{} -C ->{}".format(fin, fout.name)) + ... print(fout.read().strip()) + -180 180 -90 90 -8425 5551 1 1 361 181 """ @@ -103,7 +111,7 @@ class Session: required_version = "6.0.0" @property - def current_session(self): + def session_pointer(self): """ The C void pointer for the current open GMT session. @@ -114,7 +122,7 @@ def current_session(self): outside of the context manager). """ - if not hasattr(self, "_session_id") or self._session_id is None: + if not hasattr(self, "_session_pointer") or self._session_pointer is None: raise GMTCLibNoSessionError( " ".join( [ @@ -123,40 +131,41 @@ def current_session(self): ] ) ) - return self._session_id + return self._session_pointer - @current_session.setter - def current_session(self, session): + @session_pointer.setter + def session_pointer(self, session): """ Set the session void pointer. """ - self._session_id = session + self._session_pointer = session @property def info(self): - """ - Dictionary with the GMT version and default paths and parameters. - """ - infodict = { - "version": self.get_default("API_VERSION"), - "padding": self.get_default("API_PAD"), - "binary dir": self.get_default("API_BINDIR"), - "share dir": self.get_default("API_SHAREDIR"), - # This segfaults for some reason - # 'data dir': self.get_default("API_DATADIR"), - "plugin dir": self.get_default("API_PLUGINDIR"), - "library path": self.get_default("API_LIBRARY"), - "cores": self.get_default("API_CORES"), - "image layout": self.get_default("API_IMAGE_LAYOUT"), - "grid layout": self.get_default("API_GRID_LAYOUT"), - } - return infodict + "Dictionary with the GMT version and default paths and parameters." + if not hasattr(self, "_info"): + self._info = { + "version": self.get_default("API_VERSION"), + "padding": self.get_default("API_PAD"), + "binary dir": self.get_default("API_BINDIR"), + "share dir": self.get_default("API_SHAREDIR"), + # This segfaults for some reason + # 'data dir': self.get_default("API_DATADIR"), + "plugin dir": self.get_default("API_PLUGINDIR"), + "library path": self.get_default("API_LIBRARY"), + "cores": self.get_default("API_CORES"), + "image layout": self.get_default("API_IMAGE_LAYOUT"), + "grid layout": self.get_default("API_GRID_LAYOUT"), + } + return self._info def get_libgmt_func(self, name, argtypes=None, restype=None): """ Get a ctypes function from the libgmt shared library. - Also assigns the argument and return type conversions to the function. + Assigns the argument and return type conversions for the function. + + Use this method to access a C function from libgmt. Parameters ---------- @@ -198,7 +207,7 @@ def __enter__(self): """ Start the GMT session and keep the session argument. """ - self.current_session = self.create_session("gmt-python-session") + self.session_pointer = self.create_session("gmt-python-session") # Need to store the version info because 'get_default' won't work after # the session is destroyed. version = self.info["version"] @@ -222,9 +231,9 @@ def _cleanup_session(self): Destroy the current session and set the stored session to None """ try: - self.destroy_session(self.current_session) + self.destroy_session(self.session_pointer) finally: - self.current_session = None + self.session_pointer = None def create_session(self, session_name): """ @@ -397,7 +406,7 @@ def get_default(self, name): # Make a string buffer to get a return value value = ctypes.create_string_buffer(10000) - status = c_get_default(self.current_session, name.encode(), value) + status = c_get_default(self.session_pointer, name.encode(), value) if status != 0: raise GMTCLibError( @@ -439,7 +448,7 @@ def call_module(self, module, args): mode = self.get_constant("GMT_MODULE_CMD") status = c_call_module( - self.current_session, module.encode(), mode, args.encode() + self.session_pointer, module.encode(), mode, args.encode() ) if status != 0: raise GMTCLibError( @@ -525,7 +534,7 @@ def create_data(self, family, geometry, mode, **kwargs): # container should be created empty. Fill it in later using put_vector # and put_matrix. data_ptr = c_create_data( - self.current_session, + self.session_pointer, family_int, geometry_int, mode_int, @@ -717,7 +726,7 @@ def put_vector(self, dataset, column, vector): gmt_type = self._check_dtype_and_dim(vector, ndim=1) vector_pointer = vector.ctypes.data_as(ctypes.c_void_p) status = c_put_vector( - self.current_session, dataset, column, gmt_type, vector_pointer + self.session_pointer, dataset, column, gmt_type, vector_pointer ) if status != 0: raise GMTCLibError( @@ -781,7 +790,7 @@ def put_matrix(self, dataset, matrix, pad=0): gmt_type = self._check_dtype_and_dim(matrix, ndim=2) matrix_pointer = matrix.ctypes.data_as(ctypes.c_void_p) status = c_put_matrix( - self.current_session, dataset, gmt_type, pad, matrix_pointer + self.session_pointer, dataset, gmt_type, pad, matrix_pointer ) if status != 0: raise GMTCLibError("Failed to put matrix of type {}.".format(matrix.dtype)) @@ -844,7 +853,7 @@ def write_data(self, family, geometry, mode, wesn, output, data): family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) status = c_write_data( - self.current_session, + self.session_pointer, family_int, self.get_constant("GMT_IS_FILE"), geometry_int, @@ -949,7 +958,7 @@ def open_virtual_file(self, family, geometry, direction, data): buff = ctypes.create_string_buffer(self.get_constant("GMT_STR16")) status = c_open_virtualfile( - self.current_session, family_int, geometry_int, direction_int, data, buff + self.session_pointer, family_int, geometry_int, direction_int, data, buff ) if status != 0: @@ -960,7 +969,7 @@ def open_virtual_file(self, family, geometry, direction, data): try: yield vfname finally: - status = c_close_virtualfile(self.current_session, vfname.encode()) + status = c_close_virtualfile(self.session_pointer, vfname.encode()) if status != 0: raise GMTCLibError("Failed to close virtual file '{}'.".format(vfname)) @@ -1265,7 +1274,7 @@ def extract_region(self): # The second argument to GMT_Extract_Region is a file pointer to a # PostScript file. It's only valid in classic mode. Use None to get a # NULL pointer instead. - status = c_extract_region(self.current_session, None, wesn_pointer) + status = c_extract_region(self.session_pointer, None, wesn_pointer) if status != 0: raise GMTCLibError("Failed to extract region from current figure.") return wesn diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index 6c298d4692e..074578345f1 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -14,13 +14,8 @@ from .. import clib from ..clib.session import FAMILIES, VIAS -from ..clib.loading import ( - clib_extension, - load_libgmt, - check_libgmt, - get_clib_path, -) -from..clib.conversion import dataarray_to_matrix +from ..clib.loading import clib_extension, load_libgmt, check_libgmt, get_clib_path +from ..clib.conversion import dataarray_to_matrix from ..exceptions import ( GMTCLibError, GMTOSError, @@ -205,7 +200,7 @@ def test_method_no_session(): with pytest.raises(GMTCLibNoSessionError): lib.call_module("gmtdefaults", "") with pytest.raises(GMTCLibNoSessionError): - lib.current_session # pylint: disable=pointless-statement + lib.session_pointer # pylint: disable=pointless-statement def test_parse_constant_single(): @@ -794,8 +789,10 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument value.value = b"bla" return 0 - with clib.Session() as lib: - with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): + lib = clib.Session() + + with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): + with lib: info = lib.info # Check for an empty dictionary assert info @@ -822,4 +819,4 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument assert lib.info["version"] != "5.4.3" # Make sure the session is closed when the exception is raised. with pytest.raises(GMTCLibNoSessionError): - assert lib.current_session + assert lib.session_pointer From 149be562c5ec44f4f15a2cdc954837d1b72cc14a Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Sat, 7 Jul 2018 11:31:18 -1000 Subject: [PATCH 6/9] Add back intersphinx and update api docs --- Makefile | 2 +- doc/api/index.rst | 48 ++++++++++++--------- doc/conf.py | 105 ++++++++++++++++++++++++++++------------------ 3 files changed, 95 insertions(+), 60 deletions(-) diff --git a/Makefile b/Makefile index 4d7bb63773e..ee4762fe61b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TESTDIR=tmp-test-dir-with-unique-name PYTEST_ARGS=--doctest-modules -v --pyargs PYTEST_COV_ARGS=--cov-config=../.coveragerc --cov-report=term-missing -CHECK_FILES=gmt setup.py +CHECK_FILES=gmt setup.py doc/conf.py help: @echo "Commands:" diff --git a/doc/api/index.rst b/doc/api/index.rst index 71a73140563..4b02e1e0733 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -100,37 +100,47 @@ All custom exceptions are derived from :class:`gmt.exceptions.GMTError`. GMT C API --------- -The :mod:`gmt.clib` package is a wrapper for the GMT C API built using -`ctypes `__. -Most calls to the C API happen through the :class:`gmt.clib.LibGMT` class. +The :mod:`gmt.clib` package is a wrapper for the GMT C API built using :mod:`ctypes`. +Most calls to the C API happen through the :class:`gmt.clib.Session` class. .. autosummary:: :toctree: generated - clib.LibGMT + clib.Session -Main methods (this is what the rest of the library uses): +`GMT modules `__ are executed through +the :meth:`~gmt.clib.Session.call_module` method: .. autosummary:: :toctree: generated - clib.LibGMT.call_module - clib.LibGMT.grid_to_vfile - clib.LibGMT.vectors_to_vfile - clib.LibGMT.matrix_to_vfile - clib.LibGMT.extract_region + clib.Session.call_module + +Passing memory blocks between Python variables (:class:`numpy.ndarray`, +:class:`pandas.Series`, and :class:`xarray.DataArray`) and GMT happens through *virtual +files*. These methods are context managers that automate the conversion of Python +variables to GMT virtual files: + +.. autosummary:: + :toctree: generated + + clib.Session.grid_to_vfile + clib.Session.vectors_to_vfile + clib.Session.matrix_to_vfile + Low level access (these are mostly used by the :mod:`gmt.clib` package): .. autosummary:: :toctree: generated - clib.LibGMT.create_session - clib.LibGMT.destroy_session - clib.LibGMT.get_constant - clib.LibGMT.get_default - clib.LibGMT.create_data - clib.LibGMT.open_virtual_file - clib.LibGMT.put_matrix - clib.LibGMT.put_vector - clib.LibGMT.write_data + clib.Session.create_session + clib.Session.destroy_session + clib.Session.get_constant + clib.Session.get_default + clib.Session.create_data + clib.Session.open_virtual_file + clib.Session.put_matrix + clib.Session.put_vector + clib.Session.write_data + clib.Session.extract_region diff --git a/doc/conf.py b/doc/conf.py index 7de393b78aa..f02a62abd7d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -11,16 +11,17 @@ from gmt import __version__, __commit__ extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.doctest', - 'sphinx.ext.viewcode', - 'sphinx.ext.extlinks', - 'numpydoc', - 'nbsphinx', - 'gmt.sphinxext.gmtplot', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.doctest", + "sphinx.ext.viewcode", + "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", + "numpydoc", + "nbsphinx", + "gmt.sphinxext.gmtplot", ] # Autosummary pages will be generated by sphinx-autogen instead of sphinx-build @@ -28,36 +29,46 @@ numpydoc_class_members_toctree = False +# intersphinx configuration +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://docs.scipy.org/doc/numpy/", None), + "pandas": ("http://pandas.pydata.org/pandas-docs/stable/", None), + "xarray": ("http://xarray.pydata.org/en/stable/", None), +} + # Sphinx project configuration -templates_path = ['_templates'] -exclude_patterns = ['_build', '**.ipynb_checkpoints'] -source_suffix = '.rst' +templates_path = ["_templates"] +exclude_patterns = ["_build", "**.ipynb_checkpoints"] +source_suffix = ".rst" # The encoding of source files. -source_encoding = 'utf-8-sig' -master_doc = 'index' +source_encoding = "utf-8-sig" +master_doc = "index" # General information about the project year = datetime.date.today().year -project = u'GMT/Python' -copyright = u'2017-2018, Leonardo Uieda and Paul Wessel' -if len(__version__.split('+')) > 1 or __version__ == 'unknown': - version = 'dev' +project = u"GMT/Python" +copyright = u"2017-2018, Leonardo Uieda and Paul Wessel" +if len(__version__.split("+")) > 1 or __version__ == "unknown": + version = "dev" else: version = __version__ # These enable substitutions using |variable| in the rst files rst_epilog = """ .. |year| replace:: {year} -""".format(year=year) +""".format( + year=year +) -html_last_updated_fmt = '%b %d, %Y' -html_title = 'GMT/Python' -html_short_title = 'GMT/Python' -html_logo = '_static/gmt-python-logo.png' -html_favicon = '_static/favicon.png' -html_static_path = ['_static'] -html_extra_path = ['.nojekyll', 'CNAME'] -pygments_style = 'default' +html_last_updated_fmt = "%b %d, %Y" +html_title = "GMT/Python" +html_short_title = "GMT/Python" +html_logo = "_static/gmt-python-logo.png" +html_favicon = "_static/favicon.png" +html_static_path = ["_static"] +html_extra_path = [".nojekyll", "CNAME"] +pygments_style = "default" add_function_parentheses = False html_show_sourcelink = False html_show_sphinx = True @@ -65,22 +76,36 @@ # Theme config html_theme = "sphinx_rtd_theme" -html_theme_options = { -} +html_theme_options = {} html_context = { - 'menu_links': [ - (' Try it online!', 'http://try.gmtpython.xyz'), - (' Source Code', 'https://github.com/GenericMappingTools/gmt-python'), - (' Contributing', 'https://github.com/GenericMappingTools/gmt-python/blob/master/CONTRIBUTING.md'), - (' Code of Conduct', 'https://github.com/GenericMappingTools/gmt-python/blob/master/CODE_OF_CONDUCT.md'), - (' License', 'https://github.com/GenericMappingTools/gmt-python/blob/master/LICENSE.txt'), - (' Contact', 'https://gitter.im/GenericMappingTools/gmt-python'), + "menu_links": [ + (' Try it online!', "http://try.gmtpython.xyz"), + ( + ' Source Code', + "https://github.com/GenericMappingTools/gmt-python", + ), + ( + ' Contributing', + "https://github.com/GenericMappingTools/gmt-python/blob/master/CONTRIBUTING.md", + ), + ( + ' Code of Conduct', + "https://github.com/GenericMappingTools/gmt-python/blob/master/CODE_OF_CONDUCT.md", + ), + ( + ' License', + "https://github.com/GenericMappingTools/gmt-python/blob/master/LICENSE.txt", + ), + ( + ' Contact', + "https://gitter.im/GenericMappingTools/gmt-python", + ), ], # Custom variables to enable "Improve this page"" and "Download notebook" # links - 'doc_path': 'doc', - 'github_repo': 'GenericMappingTools/gmt-python', - 'github_version': 'master', + "doc_path": "doc", + "github_repo": "GenericMappingTools/gmt-python", + "github_version": "master", } # Load the custom CSS files (needs sphinx >= 1.6 for this to work) From 26f75a5a83c5b2d60f890cc109051d7efe746230 Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Sat, 7 Jul 2018 13:13:57 -1000 Subject: [PATCH 7/9] Clean up creating and destroying a session --- Makefile | 7 +- doc/api/index.rst | 7 +- doc/index.rst | 2 +- gmt/__init__.py | 25 ++-- gmt/clib/session.py | 287 ++++++++++++++++++++--------------------- gmt/tests/test_clib.py | 76 +++++++---- 6 files changed, 210 insertions(+), 194 deletions(-) diff --git a/Makefile b/Makefile index ee4762fe61b..ea2c214397b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TESTDIR=tmp-test-dir-with-unique-name PYTEST_ARGS=--doctest-modules -v --pyargs PYTEST_COV_ARGS=--cov-config=../.coveragerc --cov-report=term-missing -CHECK_FILES=gmt setup.py doc/conf.py +CHECK_FILES=gmt setup.py help: @echo "Commands:" @@ -28,14 +28,15 @@ test: coverage: # Run a tmp folder to make sure the tests are run on the installed version mkdir -p $(TESTDIR) - cd $(TESTDIR); python -c "import gmt; gmt.print_libgmt_info()" + @echo "" + @cd $(TESTDIR); python -c "import gmt; gmt.print_clib_info()" @echo "" cd $(TESTDIR); pytest $(PYTEST_COV_ARGS) --cov=gmt $(PYTEST_ARGS) gmt cp $(TESTDIR)/.coverage* . rm -r $(TESTDIR) format: - black $(CHECK_FILES) + black $(CHECK_FILES) doc/conf.py check: black --check $(CHECK_FILES) diff --git a/doc/api/index.rst b/doc/api/index.rst index 4b02e1e0733..3ab1766e9e1 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -62,7 +62,7 @@ Miscellaneous which test - print_libgmt_info + print_clib_info Datasets @@ -134,8 +134,8 @@ Low level access (these are mostly used by the :mod:`gmt.clib` package): .. autosummary:: :toctree: generated - clib.Session.create_session - clib.Session.destroy_session + clib.Session.create + clib.Session.destroy clib.Session.get_constant clib.Session.get_default clib.Session.create_data @@ -144,3 +144,4 @@ Low level access (these are mostly used by the :mod:`gmt.clib` package): clib.Session.put_vector clib.Session.write_data clib.Session.extract_region + clib.Session.get_libgmt_func diff --git a/doc/index.rst b/doc/index.rst index 5195764f7fe..2f32e0a748a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -66,7 +66,7 @@ Project Goals * Make GMT more accessible to new users. * Build a Pythonic API for GMT. -* Interface with the GMT C API directly using :py:mod:`ctypes` (no system calls). +* Interface with the GMT C API directly using :mod:`ctypes` (no system calls). * Support for rich display in the `Jupyter notebook `__. * Integration with the Scipy stack: :class:`numpy.ndarray` or :class:`pandas.DataFrame` for data tables and :class:`xarray.DataArray` for grids. diff --git a/gmt/__init__.py b/gmt/__init__.py index 8317cdc647b..161c18f3a43 100644 --- a/gmt/__init__.py +++ b/gmt/__init__.py @@ -27,26 +27,19 @@ _atexit.register(_end) -def print_libgmt_info(): +def print_clib_info(): """ - Print information about the currently loaded GMT shared library. + Print information about the GMT shared library that we can find. - Includes the GMT version, default values for parameters, the path to the - ``libgmt`` shared library, and GMT directories. + Includes the GMT version, default values for parameters, the path to the ``libgmt`` + shared library, and GMT directories. """ - import shutil from .clib import Session - columns = shutil.get_terminal_size().columns - title = "Currently loaded libgmt" - left = (columns - len(title) - 2) // 2 - right = left + (columns - (2 * left + len(title) + 2)) - header = " ".join(["=" * left, title, "=" * right]) - - with Session() as lib: - lines = [header] - for key in sorted(lib.info): - lines.append("{}: {}".format(key, lib.info[key])) + lines = ["Loaded libgmt:"] + with Session() as ses: + for key in sorted(ses.info): + lines.append(" {}: {}".format(key, ses.info[key])) print("\n".join(lines)) @@ -83,7 +76,7 @@ def test(doctest=True, verbose=True, coverage=False, figures=True): """ import pytest - print_libgmt_info() + print_clib_info() args = [] if verbose: diff --git a/gmt/clib/session.py b/gmt/clib/session.py index 56ef65e12f2..71ca40e367d 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -1,8 +1,9 @@ """ -ctypes wrappers for core functions from the C API +Defines the Session class to create and destroy a GMT API session and provides access to +the API functions. Uses ctypes to wrap most of the core functions from the C API. """ import sys -import ctypes +import ctypes as ctp from contextlib import contextmanager from packaging.version import Version @@ -67,9 +68,9 @@ class Session: structures to communicate data, put that code inside the same ``with`` block as the API calls that will use the data. - By default, will let ctypes try to find the GMT shared library (``libgmt``). If the - environment variable ``GMT_LIBRARY_PATH`` is set, will look for the shared library - in the directory specified by it. + By default, will let :mod:`ctypes` try to find the GMT shared library (``libgmt``). + If the environment variable ``GMT_LIBRARY_PATH`` is set, will look for the shared + library in the directory specified by it. A ``GMTVersionError`` exception will be raised if the GMT shared library reports a version < 6.0.0. @@ -77,9 +78,6 @@ class Session: The ``session_pointer`` attribute holds a ctypes pointer to the currently open session. - The context manager feature eliminates the need for the ``GMT_Create_Session`` and - ``GMT_Destroy_Session`` functions. Thus, they are not exposed in the Python API. - Raises ------ GMTCLibNotFoundError @@ -98,10 +96,16 @@ class Session: >>> grid = load_earth_relief() >>> type(grid) + >>> # Create a session and destroy it automatically when exiting the "with" block. >>> with Session() as ses: + ... # Create a virtual file and link to the memory block of the grid. ... with ses.grid_to_vfile(grid) as fin: + ... # Create a temp file to use as output. ... with GMTTempFile() as fout: + ... # Call the grdinfo module with the virtual file as input and the. + ... # temp file as output. ... ses.call_module("grdinfo", "{} -C ->{}".format(fin, fout.name)) + ... # Read the contents of the temp file before it's deleted. ... print(fout.read().strip()) -180 180 -90 90 -8425 5551 1 1 361 181 @@ -113,7 +117,7 @@ class Session: @property def session_pointer(self): """ - The C void pointer for the current open GMT session. + The :class:`ctypes.c_void_p` pointer to the current open GMT session. Raises ------ @@ -123,14 +127,7 @@ def session_pointer(self): """ if not hasattr(self, "_session_pointer") or self._session_pointer is None: - raise GMTCLibNoSessionError( - " ".join( - [ - "No currently open GMT API session.", - "Use only inside a 'with' block.", - ] - ) - ) + raise GMTCLibNoSessionError("No currently open GMT API session.") return self._session_pointer @session_pointer.setter @@ -207,12 +204,12 @@ def __enter__(self): """ Start the GMT session and keep the session argument. """ - self.session_pointer = self.create_session("gmt-python-session") - # Need to store the version info because 'get_default' won't work after - # the session is destroyed. + self.create("gmt-python-session") + # Need to store the version info because 'get_default' won't work after the + # session is destroyed. version = self.info["version"] if Version(version) < Version(self.required_version): - self._cleanup_session() + self.destroy() raise GMTVersionError( "Using an incompatible GMT version {}. Must be newer than {}.".format( version, self.required_version @@ -224,57 +221,66 @@ def __exit__(self, exc_type, exc_value, traceback): """ Destroy the session when exiting the context. """ - self._cleanup_session() + self.destroy() - def _cleanup_session(self): - """ - Destroy the current session and set the stored session to None + def create(self, name): """ - try: - self.destroy_session(self.session_pointer) - finally: - self.session_pointer = None + Create a new GMT C API session. - def create_session(self, session_name): - """ - Create the ``GMTAPI_CTRL`` struct required by the GMT C API functions. + This is required before most other methods of :class:`gmt.clib.Session` can be + called. - It is a C void pointer containing the current session information and - cannot be accessed directly. + .. warning:: + + Usage of :class:`~gmt.clib.Session` as a context manager in a ``with`` block + is preferred over calling :meth:`~gmt.clib.Session.create` and + :meth:`~gmt.clib.Session.destroy` manually. + + Calls ``GMT_Create_Session`` and generates a new ``GMTAPI_CTRL`` struct, which + is a :class:`ctypes.c_void_p` pointer. Sets the ``session_pointer`` attribute to + this pointer. - Remember to terminate the current session using - :func:`gmt.clib.Session.destroy_session` before creating a new one. + Remember to terminate the current session using :meth:`gmt.clib.Session.destroy` + before creating a new one. Parameters ---------- - session_name : str + name : str A name for this session. Doesn't really affect the outcome. - Returns - ------- - api_pointer : C void pointer (returned by ctypes as an integer) - Used by GMT C API functions. - """ + try: + # Won't raise an exception if there is a currently open session + self.session_pointer # pylint: disable=pointless-statement + # In this case, fail to create a new session until the old one is destroyed + raise GMTCLibError( + "Failed to create a GMT API session: There is a currently open session." + " Must destroy it fist." + ) + # If the exception is raised, this means that there is no open session and we're + # free to create a new one. + except GMTCLibNoSessionError: + pass + c_create_session = self.get_libgmt_func( "GMT_Create_Session", - argtypes=[ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p], - restype=ctypes.c_void_p, + argtypes=[ctp.c_char_p, ctp.c_uint, ctp.c_uint, ctp.c_void_p], + restype=ctp.c_void_p, ) # Capture the output printed by GMT into this list. Will use it later to # generate error messages for the exceptions raised by API calls. - self._log = [] + self._error_log = [] - @ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_char_p) + @ctp.CFUNCTYPE(ctp.c_int, ctp.c_void_p, ctp.c_char_p) def print_func(file_pointer, message): # pylint: disable=unused-argument """ - Callback function that GMT uses to print log and error messages. - We'll capture the message and print it to stderr so that it will show up on - the Jupyter notebook. + Callback function that the GMT C API will use to print log and error + messages. We'll capture the messages and print them to stderr so that they + will show up on the Jupyter notebook. """ message = message.decode().strip() - self._log.append(message) + self._error_log.append(message) # flush to make sure the messages are printed even if we have a crash. print(message, file=sys.stderr, flush=True) return 0 @@ -285,49 +291,58 @@ def print_func(file_pointer, message): # pylint: disable=unused-argument padding = self.get_constant("GMT_PAD_DEFAULT") session_type = self.get_constant("GMT_SESSION_EXTERNAL") - session = c_create_session( - session_name.encode(), padding, session_type, print_func - ) + session = c_create_session(name.encode(), padding, session_type, print_func) if session is None: - raise GMTCLibError("Failed to create a GMT API void pointer.") + raise GMTCLibError( + "Failed to create a GMT API session:\n{}".format(self._error_message) + ) - return session + self.session_pointer = session - def _get_error_message(self): + @property + def _error_message(self): """ - Return a string with error messages emitted by GMT. - Only includes messages with the string "[ERROR]" in them. + A string with all error messages emitted by the C API. + + Only includes messages with the string ``"[ERROR]"`` in them. """ msg = "" - if hasattr(self, "_log"): - msg = "\n".join(line for line in self._log if "[ERROR]" in line) + if hasattr(self, "_error_log"): + msg = "\n".join(line for line in self._error_log if "[ERROR]" in line) return msg - def destroy_session(self, session): + def destroy(self): """ - Terminate and free the memory of a registered ``GMTAPI_CTRL`` session. + Destroy the currently open GMT API session. - The session is created and consumed by the C API modules and needs to - be freed before creating a new. Otherwise, some of the configuration - files might be left behind and can influence subsequent API calls. + .. warning:: - Parameters - ---------- - session : C void pointer (returned by ctypes as an integer) - The active session object produced by - :func:`gmt.clib.Session.create_session`. - libgmt : :py:class:`ctypes.CDLL` - The :py:class:`ctypes.CDLL` instance for the libgmt shared library. + Usage of :class:`~gmt.clib.Session` as a context manager in a ``with`` block + is preferred over calling :meth:`~gmt.clib.Session.create` and + :meth:`~gmt.clib.Session.destroy` manually. + + Calls ``GMT_Destroy_Session`` to terminate and free the memory of a registered + ``GMTAPI_CTRL`` session (the pointer for this struct is stored in the + ``session_pointer`` attribute). + Always use this method after you are done using a C API session. The session + needs to be destroyed before creating a new one. Otherwise, some of the + configuration files might be left behind and can influence subsequent API calls. + + Sets the ``session_pointer`` attribute to ``None``. """ c_destroy_session = self.get_libgmt_func( - "GMT_Destroy_Session", argtypes=[ctypes.c_void_p], restype=ctypes.c_int + "GMT_Destroy_Session", argtypes=[ctp.c_void_p], restype=ctp.c_int ) - status = c_destroy_session(session) + status = c_destroy_session(self.session_pointer) if status: - raise GMTCLibError("Failed to destroy GMT API session") + raise GMTCLibError( + "Failed to destroy GMT API session:\n{}".format(self._error_message) + ) + + self.session_pointer = None def get_constant(self, name): """ @@ -354,7 +369,7 @@ def get_constant(self, name): """ c_get_enum = self.get_libgmt_func( - "GMT_Get_Enum", argtypes=[ctypes.c_char_p], restype=ctypes.c_int + "GMT_Get_Enum", argtypes=[ctp.c_char_p], restype=ctp.c_int ) value = c_get_enum(name.encode()) @@ -399,12 +414,12 @@ def get_default(self, name): """ c_get_default = self.get_libgmt_func( "GMT_Get_Default", - argtypes=[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p], - restype=ctypes.c_int, + argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.c_char_p], + restype=ctp.c_int, ) # Make a string buffer to get a return value - value = ctypes.create_string_buffer(10000) + value = ctp.create_string_buffer(10000) status = c_get_default(self.session_pointer, name.encode(), value) @@ -442,8 +457,8 @@ def call_module(self, module, args): """ c_call_module = self.get_libgmt_func( "GMT_Call_Module", - argtypes=[ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p], - restype=ctypes.c_int, + argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.c_int, ctp.c_void_p], + restype=ctp.c_int, ) mode = self.get_constant("GMT_MODULE_CMD") @@ -453,7 +468,7 @@ def call_module(self, module, args): if status != 0: raise GMTCLibError( "Module '{}' failed with status code {}:\n{}".format( - module, status, self._get_error_message() + module, status, self._error_message ) ) @@ -501,18 +516,18 @@ def create_data(self, family, geometry, mode, **kwargs): c_create_data = self.get_libgmt_func( "GMT_Create_Data", argtypes=[ - ctypes.c_void_p, # API - ctypes.c_uint, # family - ctypes.c_uint, # geometry - ctypes.c_uint, # mode - ctypes.POINTER(ctypes.c_uint64), # dim - ctypes.POINTER(ctypes.c_double), # range - ctypes.POINTER(ctypes.c_double), # inc - ctypes.c_uint, # registration - ctypes.c_int, # pad - ctypes.c_void_p, + ctp.c_void_p, # API + ctp.c_uint, # family + ctp.c_uint, # geometry + ctp.c_uint, # mode + ctp.POINTER(ctp.c_uint64), # dim + ctp.POINTER(ctp.c_double), # range + ctp.POINTER(ctp.c_double), # inc + ctp.c_uint, # registration + ctp.c_int, # pad + ctp.c_void_p, ], # data - restype=ctypes.c_void_p, + restype=ctp.c_void_p, ) family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) @@ -526,9 +541,9 @@ def create_data(self, family, geometry, mode, **kwargs): # Convert dim, ranges, and inc to ctypes arrays if given (will be None # if not given to represent NULL pointers) - dim = kwargs_to_ctypes_array("dim", kwargs, ctypes.c_uint64 * 4) - ranges = kwargs_to_ctypes_array("ranges", kwargs, ctypes.c_double * 4) - inc = kwargs_to_ctypes_array("inc", kwargs, ctypes.c_double * 2) + dim = kwargs_to_ctypes_array("dim", kwargs, ctp.c_uint64 * 4) + ranges = kwargs_to_ctypes_array("ranges", kwargs, ctp.c_double * 4) + inc = kwargs_to_ctypes_array("inc", kwargs, ctp.c_double * 2) # Use a NULL pointer (None) for existing data to indicate that the # container should be created empty. Fill it in later using put_vector @@ -695,7 +710,7 @@ def put_vector(self, dataset, column, vector): Parameters ---------- - dataset : :py:class:`ctypes.c_void_p` + dataset : :class:`ctypes.c_void_p` The ctypes void pointer to a ``GMT_Dataset``. Create it with :meth:`~gmt.clib.Session.create_data`. column : int @@ -713,18 +728,12 @@ def put_vector(self, dataset, column, vector): """ c_put_vector = self.get_libgmt_func( "GMT_Put_Vector", - argtypes=[ - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_void_p, - ], - restype=ctypes.c_int, + argtypes=[ctp.c_void_p, ctp.c_void_p, ctp.c_uint, ctp.c_uint, ctp.c_void_p], + restype=ctp.c_int, ) gmt_type = self._check_dtype_and_dim(vector, ndim=1) - vector_pointer = vector.ctypes.data_as(ctypes.c_void_p) + vector_pointer = vector.ctypes.data_as(ctp.c_void_p) status = c_put_vector( self.session_pointer, dataset, column, gmt_type, vector_pointer ) @@ -758,7 +767,7 @@ def put_matrix(self, dataset, matrix, pad=0): Parameters ---------- - dataset : :py:class:`ctypes.c_void_p` + dataset : :class:`ctypes.c_void_p` The ctypes void pointer to a ``GMT_Dataset``. Create it with :meth:`~gmt.clib.Session.create_data`. matrix : numpy 2d-array @@ -777,18 +786,12 @@ def put_matrix(self, dataset, matrix, pad=0): """ c_put_matrix = self.get_libgmt_func( "GMT_Put_Matrix", - argtypes=[ - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_uint, - ctypes.c_int, - ctypes.c_void_p, - ], - restype=ctypes.c_int, + argtypes=[ctp.c_void_p, ctp.c_void_p, ctp.c_uint, ctp.c_int, ctp.c_void_p], + restype=ctp.c_int, ) gmt_type = self._check_dtype_and_dim(matrix, ndim=2) - matrix_pointer = matrix.ctypes.data_as(ctypes.c_void_p) + matrix_pointer = matrix.ctypes.data_as(ctp.c_void_p) status = c_put_matrix( self.session_pointer, dataset, gmt_type, pad, matrix_pointer ) @@ -824,7 +827,7 @@ def write_data(self, family, geometry, mode, wesn, output, data): elements. output : str The output file name. - data : :py:class:`ctypes.c_void_p` + data : :class:`ctypes.c_void_p` Pointer to the data container created by :meth:`~gmt.clib.Session.create_data`. @@ -838,16 +841,16 @@ def write_data(self, family, geometry, mode, wesn, output, data): c_write_data = self.get_libgmt_func( "GMT_Write_Data", argtypes=[ - ctypes.c_void_p, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, - ctypes.POINTER(ctypes.c_double), - ctypes.c_char_p, - ctypes.c_void_p, + ctp.c_void_p, + ctp.c_uint, + ctp.c_uint, + ctp.c_uint, + ctp.c_uint, + ctp.POINTER(ctp.c_double), + ctp.c_char_p, + ctp.c_void_p, ], - restype=ctypes.c_int, + restype=ctp.c_int, ) family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) @@ -858,7 +861,7 @@ def write_data(self, family, geometry, mode, wesn, output, data): self.get_constant("GMT_IS_FILE"), geometry_int, self.get_constant(mode), - (ctypes.c_double * 6)(*wesn), + (ctp.c_double * 6)(*wesn), output.encode(), data, ) @@ -931,20 +934,20 @@ def open_virtual_file(self, family, geometry, direction, data): c_open_virtualfile = self.get_libgmt_func( "GMT_Open_VirtualFile", argtypes=[ - ctypes.c_void_p, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_void_p, - ctypes.c_char_p, + ctp.c_void_p, + ctp.c_uint, + ctp.c_uint, + ctp.c_uint, + ctp.c_void_p, + ctp.c_char_p, ], - restype=ctypes.c_int, + restype=ctp.c_int, ) c_close_virtualfile = self.get_libgmt_func( "GMT_Close_VirtualFile", - argtypes=[ctypes.c_void_p, ctypes.c_char_p], - restype=ctypes.c_int, + argtypes=[ctp.c_void_p, ctp.c_char_p], + restype=ctp.c_int, ) family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) @@ -955,7 +958,7 @@ def open_virtual_file(self, family, geometry, direction, data): valid_modifiers=["GMT_IS_REFERENCE", "GMT_IS_DUPLICATE"], ) - buff = ctypes.create_string_buffer(self.get_constant("GMT_STR16")) + buff = ctp.create_string_buffer(self.get_constant("GMT_STR16")) status = c_open_virtualfile( self.session_pointer, family_int, geometry_int, direction_int, data, buff @@ -1261,16 +1264,12 @@ def extract_region(self): """ c_extract_region = self.get_libgmt_func( "GMT_Extract_Region", - argtypes=[ - ctypes.c_void_p, - ctypes.c_char_p, - ctypes.POINTER(ctypes.c_double), - ], - restype=ctypes.c_int, + argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.POINTER(ctp.c_double)], + restype=ctp.c_int, ) wesn = np.empty(4, dtype=np.float64) - wesn_pointer = wesn.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) + wesn_pointer = wesn.ctypes.data_as(ctp.POINTER(ctp.c_double)) # The second argument to GMT_Extract_Region is a file pointer to a # PostScript file. It's only valid in classic mode. Use None to get a # NULL pointer instead. diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index 074578345f1..90538f4673c 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -32,7 +32,7 @@ @contextmanager -def mock(lib, func, returns=None, mock_func=None): +def mock(session, func, returns=None, mock_func=None): """ Mock a GMT C API function to make it always return a given value. @@ -53,7 +53,7 @@ def mock_api_function(*args): # pylint: disable=unused-argument mock_func = mock_api_function - get_libgmt_func = lib.get_libgmt_func + get_libgmt_func = session.get_libgmt_func def mock_get_libgmt_func(name, argtypes=None, restype=None): """ @@ -63,10 +63,12 @@ def mock_get_libgmt_func(name, argtypes=None, restype=None): return mock_func return get_libgmt_func(name, argtypes, restype) - setattr(lib, "get_libgmt_func", mock_get_libgmt_func) + setattr(session, "get_libgmt_func", mock_get_libgmt_func) yield + setattr(session, "get_libgmt_func", get_libgmt_func) + def test_load_libgmt(): "Test that loading libgmt works and doesn't crash." @@ -127,29 +129,50 @@ def test_constant(): def test_create_destroy_session(): "Test that create and destroy session are called without errors" - lib = clib.Session() - session1 = lib.create_session(session_name="test_session1") - assert session1 is not None - session2 = lib.create_session(session_name="test_session2") - assert session2 is not None - assert session2 != session1 - lib.destroy_session(session1) - lib.destroy_session(session2) + # Create two session and make sure they are not pointing to the same memory + session1 = clib.Session() + session1.create(name="test_session1") + assert session1.session_pointer is not None + session2 = clib.Session() + session2.create(name="test_session2") + assert session2.session_pointer is not None + assert session2.session_pointer != session1.session_pointer + session1.destroy() + session2.destroy() + # Create and destroy a session twice + ses = clib.Session() + for __ in range(2): + with pytest.raises(GMTCLibNoSessionError): + ses.session_pointer # pylint: disable=pointless-statement + ses.create("session1") + assert ses.session_pointer is not None + ses.destroy() + with pytest.raises(GMTCLibNoSessionError): + ses.session_pointer # pylint: disable=pointless-statement def test_create_session_fails(): - "Check that an exception is raised if the session pointer is None" - lib = clib.Session() - with mock(lib, "GMT_Create_Session", returns=None): + "Check that an exception is raised when failing to create a session" + ses = clib.Session() + with mock(ses, "GMT_Create_Session", returns=None): with pytest.raises(GMTCLibError): - lib.create_session("test-session-name") + ses.create("test-session-name") + # Should also fail if trying to create a session before destroying the old one. + ses.create("test1") + with pytest.raises(GMTCLibError): + ses.create("test2") def test_destroy_session_fails(): "Fail to destroy session when given bad input" - lib = clib.Session() - with pytest.raises(GMTCLibError): - lib.destroy_session(None) + ses = clib.Session() + with pytest.raises(GMTCLibNoSessionError): + ses.destroy() + ses.create("test-session") + with mock(ses, "GMT_Destroy_Session", returns=1): + with pytest.raises(GMTCLibError): + ses.destroy() + ses.destroy() def test_call_module(): @@ -789,15 +812,14 @@ def mock_defaults(api, name, value): # pylint: disable=unused-argument value.value = b"bla" return 0 - lib = clib.Session() - - with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): - with lib: - info = lib.info - # Check for an empty dictionary - assert info - for key in info: - assert info[key] == "bla" + ses = clib.Session() + ses.create("test-session") + with mock(ses, "GMT_Get_Default", mock_func=mock_defaults): + # Check for an empty dictionary + assert ses.info + for key in ses.info: + assert ses.info[key] == "bla" + ses.destroy() def test_fails_for_wrong_version(): From 5220af2d60ef835a13dfc85a7ff8febe795a43dc Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Sat, 7 Jul 2018 14:06:21 -1000 Subject: [PATCH 8/9] Make Session.get_constant __getitem__ instead --- Makefile | 9 +-- doc/api/index.rst | 6 +- gmt/clib/session.py | 158 ++++++++++++++++++++++------------------- gmt/tests/test_clib.py | 24 +++---- 4 files changed, 105 insertions(+), 92 deletions(-) diff --git a/Makefile b/Makefile index ea2c214397b..f39f10d7e8f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ TESTDIR=tmp-test-dir-with-unique-name PYTEST_ARGS=--doctest-modules -v --pyargs PYTEST_COV_ARGS=--cov-config=../.coveragerc --cov-report=term-missing -CHECK_FILES=gmt setup.py +FORMAT_FILES=gmt setup.py doc/conf.py +LINT_FILES=gmt setup.py help: @echo "Commands:" @@ -36,11 +37,11 @@ coverage: rm -r $(TESTDIR) format: - black $(CHECK_FILES) doc/conf.py + black $(FORMAT_FILES) check: - black --check $(CHECK_FILES) - pylint $(CHECK_FILES) + black --check $(FORMAT_FILES) + pylint $(LINT_FILES) clean: find . -name "*.pyc" -exec rm -v {} \; diff --git a/doc/api/index.rst b/doc/api/index.rst index 3ab1766e9e1..6c12ce2beca 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -136,12 +136,14 @@ Low level access (these are mostly used by the :mod:`gmt.clib` package): clib.Session.create clib.Session.destroy - clib.Session.get_constant + clib.Session.__getitem__ + clib.Session.__enter__ + clib.Session.__exit__ clib.Session.get_default clib.Session.create_data - clib.Session.open_virtual_file clib.Session.put_matrix clib.Session.put_vector clib.Session.write_data + clib.Session.open_virtual_file clib.Session.extract_region clib.Session.get_libgmt_func diff --git a/gmt/clib/session.py b/gmt/clib/session.py index 71ca40e367d..ffe10599305 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -156,6 +156,74 @@ def info(self): } return self._info + def __enter__(self): + """ + Create a GMT API session and check the libgmt version. + + Calls :meth:`~gmt.clib.Session.create`. + + Raises + ------ + GMTVersionError + If the version reported by libgmt is less than ``Session.required_version``. + Will destroy the session before raising the exception. + + """ + self.create("gmt-python-session") + # Need to store the version info because 'get_default' won't work after the + # session is destroyed. + version = self.info["version"] + if Version(version) < Version(self.required_version): + self.destroy() + raise GMTVersionError( + "Using an incompatible GMT version {}. Must be newer than {}.".format( + version, self.required_version + ) + ) + return self + + def __exit__(self, exc_type, exc_value, traceback): + """ + Destroy the currently open GMT API session. + + Calls :meth:`~gmt.clib.Session.destroy`. + """ + self.destroy() + + def __getitem__(self, name): + """ + Get the value of a GMT constant (C enum) from gmt_resources.h + + Used to set configuration values for other API calls. Wraps ``GMT_Get_Enum``. + + Parameters + ---------- + name : str + The name of the constant (e.g., ``"GMT_SESSION_EXTERNAL"``) + + Returns + ------- + constant : int + Integer value of the constant. Do not rely on this value because it might + change. + + Raises + ------ + GMTCLibError + If the constant doesn't exist. + + """ + c_get_enum = self.get_libgmt_func( + "GMT_Get_Enum", argtypes=[ctp.c_char_p], restype=ctp.c_int + ) + + value = c_get_enum(name.encode()) + + if value is None or value == -99999: + raise GMTCLibError("Constant '{}' doesn't exits in libgmt.".format(name)) + + return value + def get_libgmt_func(self, name, argtypes=None, restype=None): """ Get a ctypes function from the libgmt shared library. @@ -200,29 +268,6 @@ def get_libgmt_func(self, name, argtypes=None, restype=None): function.restype = restype return function - def __enter__(self): - """ - Start the GMT session and keep the session argument. - """ - self.create("gmt-python-session") - # Need to store the version info because 'get_default' won't work after the - # session is destroyed. - version = self.info["version"] - if Version(version) < Version(self.required_version): - self.destroy() - raise GMTVersionError( - "Using an incompatible GMT version {}. Must be newer than {}.".format( - version, self.required_version - ) - ) - return self - - def __exit__(self, exc_type, exc_value, traceback): - """ - Destroy the session when exiting the context. - """ - self.destroy() - def create(self, name): """ Create a new GMT C API session. @@ -289,8 +334,8 @@ def print_func(file_pointer, message): # pylint: disable=unused-argument # garbage collected otherwise self._print_callback = print_func - padding = self.get_constant("GMT_PAD_DEFAULT") - session_type = self.get_constant("GMT_SESSION_EXTERNAL") + padding = self["GMT_PAD_DEFAULT"] + session_type = self["GMT_SESSION_EXTERNAL"] session = c_create_session(name.encode(), padding, session_type, print_func) if session is None: @@ -344,41 +389,6 @@ def destroy(self): self.session_pointer = None - def get_constant(self, name): - """ - Get the value of a constant (C enum) from gmt_resources.h - - Used to set configuration values for other API calls. Wraps - ``GMT_Get_Enum``. - - Parameters - ---------- - name : str - The name of the constant (e.g., ``"GMT_SESSION_EXTERNAL"``) - - Returns - ------- - constant : int - Integer value of the constant. Do not rely on this value because it - might change. - - Raises - ------ - GMTCLibError - If the constant doesn't exist. - - """ - c_get_enum = self.get_libgmt_func( - "GMT_Get_Enum", argtypes=[ctp.c_char_p], restype=ctp.c_int - ) - - value = c_get_enum(name.encode()) - - if value is None or value == -99999: - raise GMTCLibError("Constant '{}' doesn't exits in libgmt.".format(name)) - - return value - def get_default(self, name): """ Get the value of a GMT default parameter (library version, paths, etc). @@ -461,7 +471,7 @@ def call_module(self, module, args): restype=ctp.c_int, ) - mode = self.get_constant("GMT_MODULE_CMD") + mode = self["GMT_MODULE_CMD"] status = c_call_module( self.session_pointer, module.encode(), mode, args.encode() ) @@ -579,7 +589,7 @@ def _parse_pad(self, family, kwargs): if "MATRIX" in family: pad = 0 else: - pad = self.get_constant("GMT_PAD_DEFAULT") + pad = self["GMT_PAD_DEFAULT"] return pad def _parse_constant(self, constant, valid, valid_modifiers=None): @@ -588,7 +598,7 @@ def _parse_constant(self, constant, valid, valid_modifiers=None): The GMT C API takes certain defined constants, like ``'GMT_IS_GRID'``, that need to be validated and converted to integer values using - :meth:`~gmt.clib.Session.get_constant`. + :meth:`gmt.clib.Session.__getitem__`. The constants can also take a modifier by appending another constant name, e.g. ``'GMT_IS_GRID|GMT_VIA_MATRIX'``. The two parts must be @@ -637,7 +647,7 @@ def _parse_constant(self, constant, valid, valid_modifiers=None): parts[1], str(valid_modifiers) ) ) - integer_value = sum(self.get_constant(part) for part in parts) + integer_value = sum(self[part] for part in parts) return integer_value def _check_dtype_and_dim(self, array, ndim): @@ -668,14 +678,14 @@ def _check_dtype_and_dim(self, array, ndim): >>> import numpy as np >>> data = np.array([1, 2, 3], dtype='float64') - >>> with Session() as lib: - ... gmttype = lib._check_dtype_and_dim(data, ndim=1) - ... gmttype == lib.get_constant('GMT_DOUBLE') + >>> with Session() as ses: + ... gmttype = ses._check_dtype_and_dim(data, ndim=1) + ... gmttype == ses["GMT_DOUBLE"] True >>> data = np.ones((5, 2), dtype='float32') - >>> with Session() as lib: - ... gmttype = lib._check_dtype_and_dim(data, ndim=2) - ... gmttype == lib.get_constant('GMT_FLOAT') + >>> with Session() as ses: + ... gmttype = ses._check_dtype_and_dim(data, ndim=2) + ... gmttype == ses['GMT_FLOAT'] True """ @@ -687,7 +697,7 @@ def _check_dtype_and_dim(self, array, ndim): raise GMTInvalidInput( "Expected a numpy 1d array, got {}d.".format(array.ndim) ) - return self.get_constant(DTYPES[array.dtype.name]) + return self[DTYPES[array.dtype.name]] def put_vector(self, dataset, column, vector): """ @@ -858,9 +868,9 @@ def write_data(self, family, geometry, mode, wesn, output, data): status = c_write_data( self.session_pointer, family_int, - self.get_constant("GMT_IS_FILE"), + self["GMT_IS_FILE"], geometry_int, - self.get_constant(mode), + self[mode], (ctp.c_double * 6)(*wesn), output.encode(), data, @@ -958,7 +968,7 @@ def open_virtual_file(self, family, geometry, direction, data): valid_modifiers=["GMT_IS_REFERENCE", "GMT_IS_DUPLICATE"], ) - buff = ctp.create_string_buffer(self.get_constant("GMT_STR16")) + buff = ctp.create_string_buffer(self["GMT_STR16"]) status = c_open_virtualfile( self.session_pointer, family_int, geometry_int, direction_int, data, buff diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index 90538f4673c..8b6ed394063 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -116,15 +116,15 @@ def test_clib_extension(): clib_extension("meh") -def test_constant(): +def test_getitem(): "Test that I can get correct constants from the C lib" - lib = clib.Session() - assert lib.get_constant("GMT_SESSION_EXTERNAL") != -99999 - assert lib.get_constant("GMT_MODULE_CMD") != -99999 - assert lib.get_constant("GMT_PAD_DEFAULT") != -99999 - assert lib.get_constant("GMT_DOUBLE") != -99999 + ses = clib.Session() + assert ses["GMT_SESSION_EXTERNAL"] != -99999 + assert ses["GMT_MODULE_CMD"] != -99999 + assert ses["GMT_PAD_DEFAULT"] != -99999 + assert ses["GMT_DOUBLE"] != -99999 with pytest.raises(GMTCLibError): - lib.get_constant("A_WHOLE_LOT_OF_JUNK") + ses["A_WHOLE_LOT_OF_JUNK"] # pylint: disable=pointless-statement def test_create_destroy_session(): @@ -231,7 +231,7 @@ def test_parse_constant_single(): lib = clib.Session() for family in FAMILIES: parsed = lib._parse_constant(family, valid=FAMILIES) - assert parsed == lib.get_constant(family) + assert parsed == lib[family] def test_parse_constant_composite(): @@ -240,7 +240,7 @@ def test_parse_constant_composite(): test_cases = ((family, via) for family in FAMILIES for via in VIAS) for family, via in test_cases: composite = "|".join([family, via]) - expected = lib.get_constant(family) + lib.get_constant(via) + expected = lib[family] + lib[via] parsed = lib._parse_constant(composite, valid=FAMILIES, valid_modifiers=VIAS) assert parsed == expected @@ -367,9 +367,9 @@ def test_put_vector(): x = np.array([1, 2, 3, 4, 5], dtype=dtype) y = np.array([6, 7, 8, 9, 10], dtype=dtype) z = np.array([11, 12, 13, 14, 15], dtype=dtype) - lib.put_vector(dataset, column=lib.get_constant("GMT_X"), vector=x) - lib.put_vector(dataset, column=lib.get_constant("GMT_Y"), vector=y) - lib.put_vector(dataset, column=lib.get_constant("GMT_Z"), vector=z) + lib.put_vector(dataset, column=lib["GMT_X"], vector=x) + lib.put_vector(dataset, column=lib["GMT_Y"], vector=y) + lib.put_vector(dataset, column=lib["GMT_Z"], vector=z) # Turns out wesn doesn't matter for Datasets wesn = [0] * 6 # Save the data to a file to see if it's being accessed correctly From 3e0785fc21fe366b55cc3a0df3bbc76ec650e39b Mon Sep 17 00:00:00 2001 From: Leonardo Uieda Date: Sat, 7 Jul 2018 15:10:17 -1000 Subject: [PATCH 9/9] Rename the virtual file creation methods --- doc/api/index.rst | 6 +- gmt/base_plotting.py | 6 +- gmt/clib/session.py | 187 +++++++++++++++++++---------------------- gmt/modules.py | 2 +- gmt/tests/test_clib.py | 28 +++--- 5 files changed, 107 insertions(+), 122 deletions(-) diff --git a/doc/api/index.rst b/doc/api/index.rst index 6c12ce2beca..a89c254b9f3 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -124,9 +124,9 @@ variables to GMT virtual files: .. autosummary:: :toctree: generated - clib.Session.grid_to_vfile - clib.Session.vectors_to_vfile - clib.Session.matrix_to_vfile + clib.Session.virtualfile_from_matrix + clib.Session.virtualfile_from_vectors + clib.Session.virtualfile_from_grid Low level access (these are mostly used by the :mod:`gmt.clib` package): diff --git a/gmt/base_plotting.py b/gmt/base_plotting.py index df1b51d7596..2badf4de48d 100644 --- a/gmt/base_plotting.py +++ b/gmt/base_plotting.py @@ -150,7 +150,7 @@ def grdimage(self, grid, **kwargs): if kind == "file": file_context = dummy_context(grid) elif kind == "grid": - file_context = lib.grid_to_vfile(grid) + file_context = lib.virtualfile_from_grid(grid) else: raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid))) with file_context as fname: @@ -262,9 +262,9 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): if kind == "file": file_context = dummy_context(data) elif kind == "matrix": - file_context = lib.matrix_to_vfile(data) + file_context = lib.virtualfile_from_matrix(data) elif kind == "vectors": - file_context = lib.vectors_to_vfile(x, y, *extra_arrays) + file_context = lib.virtualfile_from_vectors(x, y, *extra_arrays) with file_context as fname: arg_str = " ".join([fname, build_arg_string(kwargs)]) diff --git a/gmt/clib/session.py b/gmt/clib/session.py index ffe10599305..2d41432d8a8 100644 --- a/gmt/clib/session.py +++ b/gmt/clib/session.py @@ -99,7 +99,7 @@ class Session: >>> # Create a session and destroy it automatically when exiting the "with" block. >>> with Session() as ses: ... # Create a virtual file and link to the memory block of the grid. - ... with ses.grid_to_vfile(grid) as fin: + ... with ses.virtualfile_from_grid(grid) as fin: ... # Create a temp file to use as output. ... with GMTTempFile() as fout: ... # Call the grdinfo module with the virtual file as input and the. @@ -987,36 +987,34 @@ def open_virtual_file(self, family, geometry, direction, data): raise GMTCLibError("Failed to close virtual file '{}'.".format(vfname)) @contextmanager - def vectors_to_vfile(self, *vectors): + def virtualfile_from_vectors(self, *vectors): """ - Store 1d arrays in a GMT virtual file to use as a module input. + Store 1d arrays as columns of a table inside a virtual file. - Context manager (use in a ``with`` block). Yields the virtual file name - that you can pass as an argument to a GMT module call. Closes the - virtual file upon exit of the ``with`` block. + Use the virtual file name to pass in the data in your vectors to a GMT module. - Use this instead of creating GMT Datasets and Virtual Files by hand - with :meth:`~gmt.clib.Session.create_data`, - :meth:`~gmt.clib.Session.put_vector`, and - :meth:`~gmt.clib.Session.open_virtual_file` + Context manager (use in a ``with`` block). Yields the virtual file name that you + can pass as an argument to a GMT module call. Closes the virtual file upon exit + of the ``with`` block. - The virtual file will contain the arrays as ``GMT Vector`` structures. + Use this instead of creating the data container and virtual file by hand with + :meth:`~gmt.clib.Session.create_data`, :meth:`~gmt.clib.Session.put_vector`, and + :meth:`~gmt.clib.Session.open_virtual_file`. - If the arrays are C contiguous blocks of memory, they will be passed - without copying to GMT. If they are not (e.g., they are columns of a 2D - array), they will need to be copied to a contiguous block. + If the arrays are C contiguous blocks of memory, they will be passed without + copying to GMT. If they are not (e.g., they are columns of a 2D array), they + will need to be copied to a contiguous block. Parameters ---------- vectors : 1d arrays - The vectors that will be included in the array. All must be of the - same size. + The vectors that will be included in the array. All must be of the same + size. Yields ------ - vfile : str - The name of virtual file. Pass this as a file name argument to a - GMT module. + fname : str + The name of virtual file. Pass this as a file name argument to a GMT module. Examples -------- @@ -1027,23 +1025,21 @@ def vectors_to_vfile(self, *vectors): >>> x = [1, 2, 3] >>> y = np.array([4, 5, 6]) >>> z = pd.Series([7, 8, 9]) - >>> with Session() as lib: - ... with lib.vectors_to_vfile(x, y, z) as vfile: + >>> with Session() as ses: + ... with ses.virtualfile_from_vectors(x, y, z) as fin: ... # Send the output to a file so that we can read it - ... with GMTTempFile() as ofile: - ... args = '{} ->{}'.format(vfile, ofile.name) - ... lib.call_module('info', args) - ... print(ofile.read().strip()) + ... with GMTTempFile() as fout: + ... ses.call_module('info', '{} ->{}'.format(fin, fout.name)) + ... print(fout.read().strip()) : N = 3 <1/3> <4/6> <7/9> """ - # Conversion to a C-contiguous array needs to be done here and not in - # put_matrix because we need to maintain a reference to the copy while - # it is being used by the C API. Otherwise, the array would be garbage - # collected and the memory freed. Creating it in this context manager - # guarantees that the copy will be around until the virtual file is - # closed. - # The conversion is implicit in vectors_to_arrays. + # Conversion to a C-contiguous array needs to be done here and not in put_matrix + # because we need to maintain a reference to the copy while it is being used by + # the C API. Otherwise, the array would be garbage collected and the memory + # freed. Creating it in this context manager guarantees that the copy will be + # around until the virtual file is closed. The conversion is implicit in + # vectors_to_arrays. arrays = vectors_to_arrays(vectors) columns = len(arrays) @@ -1061,47 +1057,43 @@ def vectors_to_vfile(self, *vectors): for col, array in enumerate(arrays): self.put_vector(dataset, column=col, vector=array) - vf_args = (family, geometry, "GMT_IN", dataset) - with self.open_virtual_file(*vf_args) as vfile: + with self.open_virtual_file(family, geometry, "GMT_IN", dataset) as vfile: yield vfile @contextmanager - def matrix_to_vfile(self, matrix): + def virtualfile_from_matrix(self, matrix): """ - Store a 2d array in a GMT virtual file to use as a module input. + Store a 2d array as a table inside a virtual file. - Context manager (use in a ``with`` block). Yields the virtual file name - that you can pass as an argument to a GMT module call. Closes the - virtual file upon exit of the ``with`` block. + Use the virtual file name to pass in the data in your matrix to a GMT module. - The virtual file will contain the array as a ``GMT_MATRIX``. + Context manager (use in a ``with`` block). Yields the virtual file name that you + can pass as an argument to a GMT module call. Closes the virtual file upon exit + of the ``with`` block. - **Not meant for creating GMT Grids**. The grid requires more metadata - than just the data matrix. This creates a Dataset (table). + The virtual file will contain the array as a ``GMT_MATRIX`` pretending to be a + ``GMT_DATASET``. - Use this instead of creating GMT Datasets and Virtual Files by hand - with :meth:`~gmt.clib.Session.create_data`, - :meth:`~gmt.clib.Session.put_matrix`, and - :meth:`~gmt.clib.Session.open_virtual_file` + **Not meant for creating ``GMT_GRID``**. The grid requires more metadata than + just the data matrix. Use :meth:`~gmt.clib.Session.virtualfile_from_grid` + instead. - The matrix must be C contiguous in memory. If it is not (e.g., it is a - slice of a larger array), the array will be copied to make sure it is. + Use this instead of creating the data container and virtual file by hand with + :meth:`~gmt.clib.Session.create_data`, :meth:`~gmt.clib.Session.put_matrix`, and + :meth:`~gmt.clib.Session.open_virtual_file` - It might be more efficient than using - :meth:`~gmt.clib.Session.vectors_to_vfile` if your data are columns of a - 2D array. In these cases, ``vectors_to_vfile`` will have to duplicate - the memory of your array in order for columns to be C contiguous. + The matrix must be C contiguous in memory. If it is not (e.g., it is a slice of + a larger array), the array will be copied to make sure it is. Parameters ---------- matrix : 2d array - The matrix that will be included in the Dataset. + The matrix that will be included in the GMT data container. Yields ------ - vfile : str - The name of virtual file. Pass this as a file name argument to a - GMT module. + fname : str + The name of virtual file. Pass this as a file name argument to a GMT module. Examples -------- @@ -1114,22 +1106,20 @@ def matrix_to_vfile(self, matrix): [ 3 4 5] [ 6 7 8] [ 9 10 11]] - >>> with Session() as lib: - ... with lib.matrix_to_vfile(data) as vfile: + >>> with Session() as ses: + ... with ses.virtualfile_from_matrix(data) as fin: ... # Send the output to a file so that we can read it - ... with GMTTempFile() as ofile: - ... args = '{} ->{}'.format(vfile, ofile.name) - ... lib.call_module('info', args) - ... print(ofile.read().strip()) + ... with GMTTempFile() as fout: + ... ses.call_module('info', '{} ->{}'.format(fin, fout.name)) + ... print(fout.read().strip()) : N = 4 <0/9> <1/10> <2/11> """ - # Conversion to a C-contiguous array needs to be done here and not in - # put_matrix because we need to maintain a reference to the copy while - # it is being used by the C API. Otherwise, the array would be garbage - # collected and the memory freed. Creating it in this context manager - # guarantees that the copy will be around until the virtual file is - # closed. + # Conversion to a C-contiguous array needs to be done here and not in put_matrix + # because we need to maintain a reference to the copy while it is being used by + # the C API. Otherwise, the array would be garbage collected and the memory + # freed. Creating it in this context manager guarantees that the copy will be + # around until the virtual file is closed. matrix = as_c_contiguous(matrix) rows, columns = matrix.shape @@ -1142,43 +1132,39 @@ def matrix_to_vfile(self, matrix): self.put_matrix(dataset, matrix) - vf_args = (family, geometry, "GMT_IN", dataset) - with self.open_virtual_file(*vf_args) as vfile: + with self.open_virtual_file(family, geometry, "GMT_IN", dataset) as vfile: yield vfile @contextmanager - def grid_to_vfile(self, grid): + def virtualfile_from_grid(self, grid): """ - Store a grid in a GMT virtual file to use as a module input. + Store a grid in a virtual file. - Used to pass grid data into GMT modules. Grids must be - ``xarray.DataArray`` instances. + Use the virtual file name to pass in the data in your grid to a GMT module. + Grids must be :class:`xarray.DataArray` instances. - Context manager (use in a ``with`` block). Yields the virtual file name - that you can pass as an argument to a GMT module call. Closes the - virtual file upon exit of the ``with`` block. + Context manager (use in a ``with`` block). Yields the virtual file name that you + can pass as an argument to a GMT module call. Closes the virtual file upon exit + of the ``with`` block. - The virtual file will contain the grid as a ``GMT_MATRIX``. + The virtual file will contain the grid as a ``GMT_MATRIX`` with extra metadata. - Use this instead of creating ``GMT_GRID`` and virtual files by hand - with :meth:`~gmt.clib.Session.create_data`, - :meth:`~gmt.clib.Session.put_matrix`, and + Use this instead of creating a data container and virtual file by hand with + :meth:`~gmt.clib.Session.create_data`, :meth:`~gmt.clib.Session.put_matrix`, and :meth:`~gmt.clib.Session.open_virtual_file` - The grid data matrix must be C contiguous in memory. If it is not - (e.g., it is a slice of a larger array), the array will be copied to - make sure it is. + The grid data matrix must be C contiguous in memory. If it is not (e.g., it is a + slice of a larger array), the array will be copied to make sure it is. Parameters ---------- - grid : xarray.DataArraw + grid : :class:`xarray.DataArray` The grid that will be included in the virtual file. Yields ------ - vfile : str - The name of virtual file. Pass this as a file name argument to a - GMT module. + fname : str + The name of virtual file. Pass this as a file name argument to a GMT module. Examples -------- @@ -1194,24 +1180,23 @@ def grid_to_vfile(self, grid): -90.0 90.0 >>> print(data.values.min(), data.values.max()) -8425.0 5551.0 - >>> with Session() as lib: - ... with lib.grid_to_vfile(data) as vfile: + >>> with Session() as ses: + ... with ses.virtualfile_from_grid(data) as fin: ... # Send the output to a file so that we can read it - ... with GMTTempFile() as ofile: - ... args = '{} -L0 -Cn ->{}'.format(vfile, ofile.name) - ... lib.call_module('grdinfo', args) - ... print(ofile.read().strip()) + ... with GMTTempFile() as fout: + ... args = '{} -L0 -Cn ->{}'.format(fin, fout.name) + ... ses.call_module('grdinfo', args) + ... print(fout.read().strip()) -180 180 -90 90 -8425 5551 1 1 361 181 >>> # The output is: w e s n z0 z1 dx dy n_columns n_rows """ - # Conversion to a C-contiguous array needs to be done here and not in - # put_matrix because we need to maintain a reference to the copy while - # it is being used by the C API. Otherwise, the array would be garbage - # collected and the memory freed. Creating it in this context manager - # guarantees that the copy will be around until the virtual file is - # closed. - # The conversion is implicit in dataarray_to_matrix. + # Conversion to a C-contiguous array needs to be done here and not in put_matrix + # because we need to maintain a reference to the copy while it is being used by + # the C API. Otherwise, the array would be garbage collected and the memory + # freed. Creating it in this context manager guarantees that the copy will be + # around until the virtual file is closed. The conversion is implicit in + # dataarray_to_matrix. matrix, region, inc = dataarray_to_matrix(grid) family = "GMT_IS_GRID|GMT_VIA_MATRIX" geometry = "GMT_IS_SURFACE" diff --git a/gmt/modules.py b/gmt/modules.py index 8db4a27886b..6d5148c40af 100644 --- a/gmt/modules.py +++ b/gmt/modules.py @@ -39,7 +39,7 @@ def grdinfo(grid, **kwargs): if kind == "file": file_context = dummy_context(grid) elif kind == "grid": - file_context = lib.grid_to_vfile(grid) + file_context = lib.virtualfile_from_grid(grid) else: raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid))) with file_context as infile: diff --git a/gmt/tests/test_clib.py b/gmt/tests/test_clib.py index 8b6ed394063..7f701696fe5 100644 --- a/gmt/tests/test_clib.py +++ b/gmt/tests/test_clib.py @@ -576,7 +576,7 @@ def test_virtual_file_bad_direction(): print("This should have failed") -def test_vectors_to_vfile(): +def test_virtualfile_from_vectors(): "Test the automation for transforming vectors to virtual file dataset" dtypes = "float32 float64 int32 int64 uint32 uint64".split() size = 10 @@ -585,7 +585,7 @@ def test_vectors_to_vfile(): y = np.arange(size, size * 2, 1, dtype=dtype) z = np.arange(size * 2, size * 3, 1, dtype=dtype) with clib.Session() as lib: - with lib.vectors_to_vfile(x, y, z) as vfile: + with lib.virtualfile_from_vectors(x, y, z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) @@ -596,14 +596,14 @@ def test_vectors_to_vfile(): assert output == expected -def test_vectors_to_vfile_transpose(): +def test_virtualfile_from_vectors_transpose(): "Test transforming matrix columns to virtual file dataset" dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (7, 5) for dtype in dtypes: data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) with clib.Session() as lib: - with lib.vectors_to_vfile(*data.T) as vfile: + with lib.virtualfile_from_vectors(*data.T) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} -C ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) @@ -614,24 +614,24 @@ def test_vectors_to_vfile_transpose(): assert output == expected -def test_vectors_to_vfile_diff_size(): +def test_virtualfile_from_vectors_diff_size(): "Test the function fails for arrays of different sizes" x = np.arange(5) y = np.arange(6) with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - with lib.vectors_to_vfile(x, y): + with lib.virtualfile_from_vectors(x, y): print("This should have failed") -def test_matrix_to_vfile(): +def test_virtualfile_from_matrix(): "Test transforming a matrix to virtual file dataset" dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (7, 5) for dtype in dtypes: data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) with clib.Session() as lib: - with lib.matrix_to_vfile(data) as vfile: + with lib.virtualfile_from_matrix(data) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) @@ -642,7 +642,7 @@ def test_matrix_to_vfile(): assert output == expected -def test_matrix_to_vfile_slice(): +def test_virtualfile_from_matrix_slice(): "Test transforming a slice of a larger array to virtual file dataset" dtypes = "float32 float64 int32 int64 uint32 uint64".split() shape = (10, 6) @@ -652,7 +652,7 @@ def test_matrix_to_vfile_slice(): cols = 3 data = full_data[:rows, :cols] with clib.Session() as lib: - with lib.matrix_to_vfile(data) as vfile: + with lib.virtualfile_from_matrix(data) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) @@ -663,7 +663,7 @@ def test_matrix_to_vfile_slice(): assert output == expected -def test_vectors_to_vfile_pandas(): +def test_virtualfile_from_vectors_pandas(): "Pass vectors to a dataset using pandas Series" dtypes = "float32 float64 int32 int64 uint32 uint64".split() size = 13 @@ -676,7 +676,7 @@ def test_vectors_to_vfile_pandas(): ) ) with clib.Session() as lib: - with lib.vectors_to_vfile(data.x, data.y, data.z) as vfile: + with lib.virtualfile_from_vectors(data.x, data.y, data.z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True) @@ -690,14 +690,14 @@ def test_vectors_to_vfile_pandas(): assert output == expected -def test_vectors_to_vfile_arraylike(): +def test_virtualfile_from_vectors_arraylike(): "Pass array-like vectors to a dataset" size = 13 x = list(range(0, size, 1)) y = tuple(range(size, size * 2, 1)) z = range(size * 2, size * 3, 1) with clib.Session() as lib: - with lib.vectors_to_vfile(x, y, z) as vfile: + with lib.virtualfile_from_vectors(x, y, z) as vfile: with GMTTempFile() as outfile: lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) output = outfile.read(keep_tabs=True)