diff --git a/doc/source/api_reference/utilities.rst b/doc/source/api_reference/utilities.rst index c0d3869e..fc28a890 100644 --- a/doc/source/api_reference/utilities.rst +++ b/doc/source/api_reference/utilities.rst @@ -50,6 +50,14 @@ General Methods .. autofunction:: pyTMD.utilities.from_ftp +.. autofunction:: pyTMD.utilities._create_default_ssl_context + +.. autofunction:: pyTMD.utilities._create_ssl_context_no_verify + +.. autofunction:: pyTMD.utilities._set_ssl_context_options + +.. autofunction:: pyTMD.utilities.check_connection + .. autofunction:: pyTMD.utilities.http_list .. autofunction:: pyTMD.utilities.from_http diff --git a/pyTMD/constants.py b/pyTMD/constants.py index f26fb3b5..28ea6093 100755 --- a/pyTMD/constants.py +++ b/pyTMD/constants.py @@ -283,7 +283,7 @@ def q(self) -> float: # p. 67, Eqn.(2-113) @property def q0(self) -> float: - """q\ :sub:`0` Parameter + r"""q\ :sub:`0` Parameter """ return 3*(1.0 + 1.0/(self.ecc2**2)) * \ (1.0 -1.0/self.ecc2*np.arctan(self.ecc2)) - 1.0 @@ -299,7 +299,7 @@ def J2(self) -> float: # p. 60, Eqn.(2-80) @property def C20(self) -> float: - """Normalized C\ :sub:`20` harmonic + r"""Normalized C\ :sub:`20` harmonic """ return -self.J2/np.sqrt(5.0) diff --git a/pyTMD/io/model.py b/pyTMD/io/model.py index d53e441e..ec148d37 100644 --- a/pyTMD/io/model.py +++ b/pyTMD/io/model.py @@ -1,11 +1,12 @@ #!/usr/bin/env python u""" model.py -Written by Tyler Sutterley (09/2023) +Written by Tyler Sutterley (11/2023) Retrieves tide model parameters for named tide models and from model definition files UPDATE HISTORY: + Updated 11/2023: revert TPXO9-atlas currents changes to separate dicts Updated 09/2023: fix scale values for TPXO9-atlas currents Updated 08/2023: changed ESR netCDF4 format to TMD3 format updated filenames for CATS2008-v2023 to final version @@ -718,14 +719,22 @@ def current(self, m: str): elif (m == 'TPXO9-atlas'): self.model_directory = self.directory.joinpath('TPXO9_atlas') self.grid_file = self.pathfinder('grid_tpxo9_atlas') - model_files = ['u_q1_tpxo9_atlas_30','u_o1_tpxo9_atlas_30', + model_files = {} + model_files['u'] = ['u_q1_tpxo9_atlas_30','u_o1_tpxo9_atlas_30', 'u_p1_tpxo9_atlas_30','u_k1_tpxo9_atlas_30', 'u_n2_tpxo9_atlas_30','u_m2_tpxo9_atlas_30', 'u_s2_tpxo9_atlas_30','u_k2_tpxo9_atlas_30', 'u_m4_tpxo9_atlas_30','u_ms4_tpxo9_atlas_30', 'u_mn4_tpxo9_atlas_30','u_2n2_tpxo9_atlas_30'] - self.model_file = dict(u=self.pathfinder(model_files), - v=self.pathfinder(model_files)) + model_files['v'] = ['v_q1_tpxo9_atlas_30','v_o1_tpxo9_atlas_30', + 'v_p1_tpxo9_atlas_30','v_k1_tpxo9_atlas_30', + 'v_n2_tpxo9_atlas_30','v_m2_tpxo9_atlas_30', + 'v_s2_tpxo9_atlas_30','v_k2_tpxo9_atlas_30', + 'v_m4_tpxo9_atlas_30','v_ms4_tpxo9_atlas_30', + 'v_mn4_tpxo9_atlas_30','v_2n2_tpxo9_atlas_30'] + self.model_file = {} + for key,val in model_files.items(): + self.model_file[key] = self.pathfinder(val) self.projection = '4326' self.scale = 1e-4 self.version = 'v1' @@ -735,14 +744,22 @@ def current(self, m: str): elif (m == 'TPXO9-atlas-v2'): self.model_directory = self.directory.joinpath('TPXO9_atlas_v2') self.grid_file = self.pathfinder('grid_tpxo9_atlas_30_v2') - model_files = ['u_q1_tpxo9_atlas_30_v2','u_o1_tpxo9_atlas_30_v2', + model_files = {} + model_files['u'] = ['u_q1_tpxo9_atlas_30_v2','u_o1_tpxo9_atlas_30_v2', 'u_p1_tpxo9_atlas_30_v2','u_k1_tpxo9_atlas_30_v2', 'u_n2_tpxo9_atlas_30_v2','u_m2_tpxo9_atlas_30_v2', 'u_s2_tpxo9_atlas_30_v2','u_k2_tpxo9_atlas_30_v2', 'u_m4_tpxo9_atlas_30_v2','u_ms4_tpxo9_atlas_30_v2', 'u_mn4_tpxo9_atlas_30_v2','u_2n2_tpxo9_atlas_30_v2'] - self.model_file = dict(u=self.pathfinder(model_files), - v=self.pathfinder(model_files)) + model_files['v'] = ['v_q1_tpxo9_atlas_30_v2','v_o1_tpxo9_atlas_30_v2', + 'v_p1_tpxo9_atlas_30_v2','v_k1_tpxo9_atlas_30_v2', + 'v_n2_tpxo9_atlas_30_v2','v_m2_tpxo9_atlas_30_v2', + 'v_s2_tpxo9_atlas_30_v2','v_k2_tpxo9_atlas_30_v2', + 'v_m4_tpxo9_atlas_30_v2','v_ms4_tpxo9_atlas_30_v2', + 'v_mn4_tpxo9_atlas_30_v2','v_2n2_tpxo9_atlas_30_v2'] + self.model_file = {} + for key,val in model_files.items(): + self.model_file[key] = self.pathfinder(val) self.projection = '4326' self.scale = 1e-4 self.version = 'v2' @@ -751,15 +768,24 @@ def current(self, m: str): elif (m == 'TPXO9-atlas-v3'): self.model_directory = self.directory.joinpath('TPXO9_atlas_v3') self.grid_file = self.pathfinder('grid_tpxo9_atlas_30_v3') - model_files = ['u_q1_tpxo9_atlas_30_v3','u_o1_tpxo9_atlas_30_v3', + model_files = {} + model_files['u'] = ['u_q1_tpxo9_atlas_30_v3','u_o1_tpxo9_atlas_30_v3', 'u_p1_tpxo9_atlas_30_v3','u_k1_tpxo9_atlas_30_v3', 'u_n2_tpxo9_atlas_30_v3','u_m2_tpxo9_atlas_30_v3', 'u_s2_tpxo9_atlas_30_v3','u_k2_tpxo9_atlas_30_v3', 'u_m4_tpxo9_atlas_30_v3','u_ms4_tpxo9_atlas_30_v3', 'u_mn4_tpxo9_atlas_30_v3','u_2n2_tpxo9_atlas_30_v3', 'u_mf_tpxo9_atlas_30_v3','u_mm_tpxo9_atlas_30_v3'] - self.model_file = dict(u=self.pathfinder(model_files), - v=self.pathfinder(model_files)) + model_files['v'] = ['v_q1_tpxo9_atlas_30_v3','v_o1_tpxo9_atlas_30_v3', + 'v_p1_tpxo9_atlas_30_v3','v_k1_tpxo9_atlas_30_v3', + 'v_n2_tpxo9_atlas_30_v3','v_m2_tpxo9_atlas_30_v3', + 'v_s2_tpxo9_atlas_30_v3','v_k2_tpxo9_atlas_30_v3', + 'v_m4_tpxo9_atlas_30_v3','v_ms4_tpxo9_atlas_30_v3', + 'v_mn4_tpxo9_atlas_30_v3','v_2n2_tpxo9_atlas_30_v3', + 'v_mf_tpxo9_atlas_30_v3','v_mm_tpxo9_atlas_30_v3'] + self.model_file = {} + for key,val in model_files.items(): + self.model_file[key] = self.pathfinder(val) self.projection = '4326' self.scale = 1e-4 self.version = 'v3' @@ -768,15 +794,24 @@ def current(self, m: str): elif (m == 'TPXO9-atlas-v4'): self.model_directory = self.directory.joinpath('TPXO9_atlas_v4') self.grid_file = self.pathfinder('grid_tpxo9_atlas_30_v4') - model_files = ['u_q1_tpxo9_atlas_30_v4','u_o1_tpxo9_atlas_30_v4', + model_files = {} + model_files['u'] = ['u_q1_tpxo9_atlas_30_v4','u_o1_tpxo9_atlas_30_v4', 'u_p1_tpxo9_atlas_30_v4','u_k1_tpxo9_atlas_30_v4', 'u_n2_tpxo9_atlas_30_v4','u_m2_tpxo9_atlas_30_v4', 'u_s2_tpxo9_atlas_30_v4','u_k2_tpxo9_atlas_30_v4', 'u_m4_tpxo9_atlas_30_v4','u_ms4_tpxo9_atlas_30_v4', 'u_mn4_tpxo9_atlas_30_v4','u_2n2_tpxo9_atlas_30_v4', 'u_mf_tpxo9_atlas_30_v4','u_mm_tpxo9_atlas_30_v4'] - self.model_file = dict(u=self.pathfinder(model_files), - v=self.pathfinder(model_files)) + model_files['v'] = ['v_q1_tpxo9_atlas_30_v4','v_o1_tpxo9_atlas_30_v4', + 'v_p1_tpxo9_atlas_30_v4','v_k1_tpxo9_atlas_30_v4', + 'v_n2_tpxo9_atlas_30_v4','v_m2_tpxo9_atlas_30_v4', + 'v_s2_tpxo9_atlas_30_v4','v_k2_tpxo9_atlas_30_v4', + 'v_m4_tpxo9_atlas_30_v4','v_ms4_tpxo9_atlas_30_v4', + 'v_mn4_tpxo9_atlas_30_v4','v_2n2_tpxo9_atlas_30_v4', + 'v_mf_tpxo9_atlas_30_v4','v_mm_tpxo9_atlas_30_v4'] + self.model_file = {} + for key,val in model_files.items(): + self.model_file[key] = self.pathfinder(val) self.projection = '4326' self.scale = 1e-4 self.version = 'v4' @@ -785,7 +820,8 @@ def current(self, m: str): elif (m == 'TPXO9-atlas-v5'): self.model_directory = self.directory.joinpath('TPXO9_atlas_v5') self.grid_file = self.pathfinder('grid_tpxo9_atlas_30_v5') - model_files = ['u_q1_tpxo9_atlas_30_v5','u_o1_tpxo9_atlas_30_v5', + model_files = {} + model_files['u'] = ['u_q1_tpxo9_atlas_30_v5','u_o1_tpxo9_atlas_30_v5', 'u_p1_tpxo9_atlas_30_v5','u_k1_tpxo9_atlas_30_v5', 'u_n2_tpxo9_atlas_30_v5','u_m2_tpxo9_atlas_30_v5', 'u_s1_tpxo9_atlas_30_v5','u_s2_tpxo9_atlas_30_v5', @@ -793,8 +829,17 @@ def current(self, m: str): 'u_ms4_tpxo9_atlas_30_v5','u_mn4_tpxo9_atlas_30_v5', 'u_2n2_tpxo9_atlas_30_v5','u_mf_tpxo9_atlas_30_v5', 'u_mm_tpxo9_atlas_30_v5'] - self.model_file = dict(u=self.pathfinder(model_files), - v=self.pathfinder(model_files)) + model_files['v'] = ['v_q1_tpxo9_atlas_30_v5','v_o1_tpxo9_atlas_30_v5', + 'v_p1_tpxo9_atlas_30_v5','v_k1_tpxo9_atlas_30_v5', + 'v_n2_tpxo9_atlas_30_v5','v_m2_tpxo9_atlas_30_v5', + 'v_s1_tpxo9_atlas_30_v5','v_s2_tpxo9_atlas_30_v5', + 'v_k2_tpxo9_atlas_30_v5','v_m4_tpxo9_atlas_30_v5', + 'v_ms4_tpxo9_atlas_30_v5','v_mn4_tpxo9_atlas_30_v5', + 'v_2n2_tpxo9_atlas_30_v5','v_mf_tpxo9_atlas_30_v5', + 'v_mm_tpxo9_atlas_30_v5'] + self.model_file = {} + for key,val in model_files.items(): + self.model_file[key] = self.pathfinder(val) self.projection = '4326' self.scale = 1e-4 self.version = 'v5' @@ -1278,7 +1323,7 @@ def from_file(self, definition_file: str | pathlib.Path | io.IOBase): # model files can be comma, tab or space delimited # extract full path to tide model files # extract full path to tide grid file - if temp.format in ('OTIS','ATLAS','TMD3','netcdf'): + if temp.format in ('OTIS','ATLAS','TMD3'): assert temp.grid_file # check if grid file is relative if (temp.directory is not None): @@ -1324,6 +1369,53 @@ def from_file(self, definition_file: str | pathlib.Path | io.IOBase): # fully defined single file case temp.model_file = pathlib.Path(temp.model_file).expanduser() temp.model_directory = temp.model_file.parent + elif temp.format in ('netcdf',): + assert temp.grid_file + # check if grid file is relative + if (temp.directory is not None): + temp.grid_file = temp.directory.joinpath(temp.grid_file).resolve() + else: + temp.grid_file = pathlib.Path(temp.grid_file).expanduser() + # extract model files + if (temp.type == ['u','v']) and (temp.directory is not None): + # split model file string at semicolon + model_file = temp.model_file.split(';') + glob_string = dict(u=model_file[0], v=model_file[1]) + # use glob strings to find files in directory + temp.model_file = {} + temp.model_file['u'] = list(temp.directory.glob(glob_string['u'])) + temp.model_file['v'] = list(temp.directory.glob(glob_string['v'])) + # attempt to extract model directory + try: + temp.model_directory = temp.model_file['u'][0].parent + except (IndexError, AttributeError) as exc: + message = f'No model files found with {glob_string}' + raise FileNotFoundError(message) from exc + elif (temp.type == 'z') and (temp.directory is not None): + # use glob strings to find files in directory + glob_string = copy.copy(temp.model_file) + temp.model_file = list(temp.directory.glob(glob_string)) + # attempt to extract model directory + try: + temp.model_directory = temp.model_file[0].parent + except (IndexError, AttributeError) as exc: + message = f'No model files found with {glob_string}' + raise FileNotFoundError(message) from exc + elif (temp.type == ['u','v']): + # split model file string at semicolon + model_file = temp.model_file.split(';') + # split model into list of files for each direction + temp.model_file = {} + temp.model_file['u'] = [pathlib.Path(f).expanduser() for f in + re.split(r'[\s\,]+', model_file[0])] + temp.model_file['v'] = [pathlib.Path(f).expanduser() for f in + re.split(r'[\s\,]+', model_file[1])] + # copy to directory dictionaries + temp.model_directory = temp.model_file['u'][0].parent + elif (temp.type == 'z'): + temp.model_file = [pathlib.Path(f).expanduser() for f in + re.split(r'[\s\,]+', temp.model_file)] + temp.model_directory = temp.model_file[0].parent elif temp.format in ('FES','GOT'): # extract model files if (temp.type == ['u','v']) and (temp.directory is not None): diff --git a/pyTMD/predict.py b/pyTMD/predict.py index 678b12ed..08c3cecf 100644 --- a/pyTMD/predict.py +++ b/pyTMD/predict.py @@ -898,7 +898,7 @@ def _latitude_dependence( F2_solar: np.ndarray, F2_lunar: np.ndarray ): - """ + r""" Computes the corrections induced by the latitude of the dependence given by L\ :sup:`1` diff --git a/pyTMD/utilities.py b/pyTMD/utilities.py index 6c568611..4193ec79 100644 --- a/pyTMD/utilities.py +++ b/pyTMD/utilities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" utilities.py -Written by Tyler Sutterley (06/2023) +Written by Tyler Sutterley (11/2023) Download and management utilities for syncing time and auxiliary files PYTHON DEPENDENCIES: @@ -9,6 +9,7 @@ https://pypi.python.org/pypi/lxml UPDATE HISTORY: + Updated 11/2023: updated ssl context to fix deprecation error Updated 06/2023: add functions to retrieve and revoke Earthdata tokens Updated 05/2023: add reify decorator for evaluation of properties make urs a keyword argument in CCDIS list and download functions @@ -604,11 +605,41 @@ def from_ftp( remote_buffer.seek(0) return remote_buffer +def _create_default_ssl_context() -> ssl.SSLContext: + """Creates the default SSL context + """ + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + _set_ssl_context_options(context) + context.options |= ssl.OP_NO_COMPRESSION + return context + +def _create_ssl_context_no_verify() -> ssl.SSLContext: + """Creates an SSL context for unverified connections + """ + context = _create_default_ssl_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + return context + +def _set_ssl_context_options(context: ssl.SSLContext) -> None: + """Sets the default options for the SSL context + """ + if sys.version_info >= (3, 10) or ssl.OPENSSL_VERSION_INFO >= (1, 1, 0, 7): + context.minimum_version = ssl.TLSVersion.TLSv1_2 + else: + context.options |= ssl.OP_NO_SSLv2 + context.options |= ssl.OP_NO_SSLv3 + context.options |= ssl.OP_NO_TLSv1 + context.options |= ssl.OP_NO_TLSv1_1 + # default ssl context -_default_ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS) +_default_ssl_context = _create_ssl_context_no_verify() # PURPOSE: check internet connection -def check_connection(HOST: str, context=_default_ssl_context): +def check_connection( + HOST: str, + context: ssl.SSLContext = _default_ssl_context, + ): """ Check internet connection with http host @@ -616,7 +647,7 @@ def check_connection(HOST: str, context=_default_ssl_context): ---------- HOST: str remote http host - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object """ # attempt to connect to http host @@ -635,7 +666,7 @@ def check_connection(HOST: str, context=_default_ssl_context): def http_list( HOST: str | list, timeout: int | None = None, - context = _default_ssl_context, + context: ssl.SSLContext = _default_ssl_context, parser = lxml.etree.HTMLParser(), format: str = '%Y-%m-%d %H:%M', pattern: str = '', @@ -650,7 +681,7 @@ def http_list( remote http host path timeout: int or NoneType, default None timeout in seconds for blocking operations - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object parser: obj, default lxml.etree.HTMLParser() HTML parser for ``lxml`` @@ -709,7 +740,7 @@ def http_list( def from_http( HOST: str | list, timeout: int | None = None, - context = _default_ssl_context, + context: ssl.SSLContext = _default_ssl_context, local: str | pathlib.Path | None = None, hash: str = '', chunk: int = 16384, @@ -726,7 +757,7 @@ def from_http( remote http host path split as list timeout: int or NoneType, default None timeout in seconds for blocking operations - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object local: str, pathlib.Path or NoneType, default None path to local file @@ -804,7 +835,7 @@ def attempt_login( ---------- urs: str Earthdata login URS 3 host - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object password_manager: bool, default True Create password manager context using default realm @@ -891,7 +922,7 @@ def build_opener( NASA Earthdata username password: str or NoneType, default None NASA Earthdata password - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object password_manager: bool, default True Create password manager context using default realm @@ -1319,7 +1350,7 @@ def from_cddis( def iers_list( HOST: str | list, timeout: int | None = None, - context = _default_ssl_context, + context: ssl.SSLContext = _default_ssl_context, parser = lxml.etree.HTMLParser() ): """ @@ -1331,7 +1362,7 @@ def iers_list( remote http host path timeout: int or NoneType, default None timeout in seconds for blocking operations - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object parser: obj, default lxml.etree.HTMLParser() HTML parser for ``lxml`` @@ -1372,7 +1403,7 @@ def iers_list( def from_jpl_ssd( kernel='de440s.bsp', timeout: int | None = None, - context = _default_ssl_context, + context: ssl.SSLContext = _default_ssl_context, local: str | pathlib.Path | None = None, hash: str = '', chunk: int = 16384, @@ -1391,7 +1422,7 @@ def from_jpl_ssd( JPL kernel file to download timeout: int or NoneType, default None timeout in seconds for blocking operations - context: obj, default ssl.SSLContext(ssl.PROTOCOL_TLS) + context: obj, default pyTMD.utilities._default_ssl_context SSL context for ``urllib`` opener object hash: str, default '' MD5 hash of local file diff --git a/pyTMD/version.py b/pyTMD/version.py index 26e046eb..d12ade6c 100644 --- a/pyTMD/version.py +++ b/pyTMD/version.py @@ -10,6 +10,6 @@ # get version version = metadata["version"] # append "v" before the version -full_version = "v{0}".format(version) +full_version = f"v{version}" # get project name project_name = metadata["Name"] diff --git a/test/model_TPXO9-atlas-v5_currents.def b/test/model_TPXO9-atlas-v5_currents.def index 1d5677a3..68bf6d2d 100644 --- a/test/model_TPXO9-atlas-v5_currents.def +++ b/test/model_TPXO9-atlas-v5_currents.def @@ -1,6 +1,6 @@ format netcdf name TPXO9-atlas-v5 -model_file TPXO9_atlas_v5/u_2n2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_k1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_k2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_m2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_m4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_mf_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_mm_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_mn4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_ms4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_n2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_o1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_p1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_q1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_s1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_s2_tpxo9_atlas_30_v5.nc +model_file TPXO9_atlas_v5/u_2n2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_k1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_k2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_m2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_m4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_mf_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_mm_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_mn4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_ms4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_n2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_o1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_p1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_q1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_s1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/u_s2_tpxo9_atlas_30_v5.nc;TPXO9_atlas_v5/v_2n2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_k1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_k2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_m2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_m4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_mf_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_mm_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_mn4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_ms4_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_n2_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_o1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_p1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_q1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_s1_tpxo9_atlas_30_v5.nc,TPXO9_atlas_v5/v_s2_tpxo9_atlas_30_v5.nc grid_file TPXO9_atlas_v5/grid_tpxo9_atlas_30_v5.nc type u,v version v5 diff --git a/test/test_model.py b/test/test_model.py index 12b742b5..7398894c 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -153,7 +153,8 @@ def test_definition_FES_currents(): # test read variables assert m.format == 'FES' assert m.name == 'FES2014' - model_files_u = ['fes2014/eastward_velocity/2n2.nc.gz', + model_files = {} + model_files['u'] = ['fes2014/eastward_velocity/2n2.nc.gz', 'fes2014/eastward_velocity/eps2.nc.gz', 'fes2014/eastward_velocity/j1.nc.gz', 'fes2014/eastward_velocity/k1.nc.gz', 'fes2014/eastward_velocity/k2.nc.gz', 'fes2014/eastward_velocity/l2.nc.gz', 'fes2014/eastward_velocity/la2.nc.gz', @@ -171,7 +172,7 @@ def test_definition_FES_currents(): 'fes2014/eastward_velocity/s2.nc.gz', 'fes2014/eastward_velocity/s4.nc.gz', 'fes2014/eastward_velocity/sa.nc.gz', 'fes2014/eastward_velocity/ssa.nc.gz', 'fes2014/eastward_velocity/t2.nc.gz'] - model_files_v = ['fes2014/northward_velocity/2n2.nc.gz', + model_files['v'] = ['fes2014/northward_velocity/2n2.nc.gz', 'fes2014/northward_velocity/eps2.nc.gz', 'fes2014/northward_velocity/j1.nc.gz', 'fes2014/northward_velocity/k1.nc.gz', 'fes2014/northward_velocity/k2.nc.gz', 'fes2014/northward_velocity/l2.nc.gz', 'fes2014/northward_velocity/la2.nc.gz', @@ -190,10 +191,9 @@ def test_definition_FES_currents(): 'fes2014/northward_velocity/sa.nc.gz', 'fes2014/northward_velocity/ssa.nc.gz', 'fes2014/northward_velocity/t2.nc.gz'] # assert that all model files are in the model definition - for f in model_files_u: - assert pathlib.Path(f) in m.model_file['u'] - for f in model_files_v: - assert pathlib.Path(f) in m.model_file['v'] + for t in ['u','v']: + for f in model_files[t]: + assert pathlib.Path(f) in m.model_file[t] # assert that all constituents are in the model definition constituents = ['2n2','eps2','j1','k1','k2','l2', 'lambda2','m2','m3','m4','m6','m8','mf','mks2','mm', @@ -204,7 +204,7 @@ def test_definition_FES_currents(): assert m.scale == 1.0 assert m.compressed is True # check validity of parsed constituents - parsed_constituents = [pyTMD.io.model.parse_file(f) for f in model_files_u] + parsed_constituents = [pyTMD.io.model.parse_file(f) for f in model_files['u']] assert parsed_constituents == constituents # test derived properties assert m.long_name['u'] == 'zonal_tidal_current' @@ -217,7 +217,8 @@ def test_definition_FES_currents_glob(): """ # get model parameters m = pyTMD.io.model().from_file(filepath.joinpath('model_FES2014_currents.def')) - model_files_u = ['fes2014/eastward_velocity/2n2.nc.gz', + model_files = {} + model_files['u'] = ['fes2014/eastward_velocity/2n2.nc.gz', 'fes2014/eastward_velocity/eps2.nc.gz', 'fes2014/eastward_velocity/j1.nc.gz', 'fes2014/eastward_velocity/k1.nc.gz', 'fes2014/eastward_velocity/k2.nc.gz', 'fes2014/eastward_velocity/l2.nc.gz', 'fes2014/eastward_velocity/la2.nc.gz', @@ -235,7 +236,7 @@ def test_definition_FES_currents_glob(): 'fes2014/eastward_velocity/s2.nc.gz', 'fes2014/eastward_velocity/s4.nc.gz', 'fes2014/eastward_velocity/sa.nc.gz', 'fes2014/eastward_velocity/ssa.nc.gz', 'fes2014/eastward_velocity/t2.nc.gz'] - model_files_v = ['fes2014/northward_velocity/2n2.nc.gz', + model_files['v'] = ['fes2014/northward_velocity/2n2.nc.gz', 'fes2014/northward_velocity/eps2.nc.gz', 'fes2014/northward_velocity/j1.nc.gz', 'fes2014/northward_velocity/k1.nc.gz', 'fes2014/northward_velocity/k2.nc.gz', 'fes2014/northward_velocity/l2.nc.gz', 'fes2014/northward_velocity/la2.nc.gz', @@ -254,14 +255,11 @@ def test_definition_FES_currents_glob(): 'fes2014/northward_velocity/sa.nc.gz', 'fes2014/northward_velocity/ssa.nc.gz', 'fes2014/northward_velocity/t2.nc.gz'] # create temporary files for testing glob functionality - for model_file in model_files_u: - local = filepath.joinpath(model_file) - local.parent.mkdir(parents=True, exist_ok=True) - local.touch(exist_ok=True) - for model_file in model_files_v: - local = filepath.joinpath(model_file) - local.parent.mkdir(parents=True, exist_ok=True) - local.touch(exist_ok=True) + for t in ['u','v']: + for model_file in model_files[t]: + local = filepath.joinpath(model_file) + local.parent.mkdir(parents=True, exist_ok=True) + local.touch(exist_ok=True) # create model definition file fid = io.StringIO() attrs = ['name','format','compressed','type','scale','version'] @@ -281,12 +279,10 @@ def test_definition_FES_currents_glob(): for attr in attrs: assert getattr(model,attr) == getattr(m,attr) # verify that the model files and constituents match - assert (len(model.model_file['u']) == len(model_files_u)) - assert (len(model.model_file['v']) == len(model_files_v)) - for f in model_files_u: - assert pathlib.Path(filepath).joinpath(f) in model.model_file['u'] - for f in model_files_v: - assert pathlib.Path(filepath).joinpath(f) in model.model_file['v'] + for t in ['u','v']: + assert (len(model.model_file[t]) == len(model_files[t])) + for f in model_files[t]: + assert pathlib.Path(filepath).joinpath(f) in model.model_file[t] for c in m.constituents: assert c in model.constituents # close the glob definition file @@ -482,7 +478,8 @@ def test_definition_TPXO9_currents(): # test read variables assert m.format == 'netcdf' assert m.name == 'TPXO9-atlas-v5' - model_files = ['TPXO9_atlas_v5/u_2n2_tpxo9_atlas_30_v5.nc', + model_files = {} + model_files['u'] = ['TPXO9_atlas_v5/u_2n2_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_k1_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_k2_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_m2_tpxo9_atlas_30_v5.nc', @@ -497,8 +494,24 @@ def test_definition_TPXO9_currents(): 'TPXO9_atlas_v5/u_q1_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_s1_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_s2_tpxo9_atlas_30_v5.nc'] + model_files['v'] = ['TPXO9_atlas_v5/v_2n2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_k1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_k2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_m2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_m4_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_mf_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_mm_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_mn4_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_ms4_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_n2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_o1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_p1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_q1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_s1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_s2_tpxo9_atlas_30_v5.nc'] assert m.grid_file == pathlib.Path('TPXO9_atlas_v5/grid_tpxo9_atlas_30_v5.nc') - assert sorted(m.model_file['u']) == [pathlib.Path(f) for f in model_files] + for t in ['u','v']: + assert sorted(m.model_file[t]) == [pathlib.Path(f) for f in model_files[t]] assert m.type == ['u', 'v'] assert m.scale == 1.0/100.0 assert m.compressed is False @@ -507,12 +520,13 @@ def test_definition_TPXO9_currents(): assert m.long_name['v'] == 'meridional_tidal_current' # PURPOSE: test glob file functionality -def test_definition_TPXO9_glob(): +def test_definition_TPXO9_currents_glob(): """Tests the reading of the TPXO9-atlas-v5 model definition file for currents with glob file searching """ m = pyTMD.io.model().from_file(filepath.joinpath('model_TPXO9-atlas-v5_currents.def')) - model_files = ['TPXO9_atlas_v5/u_2n2_tpxo9_atlas_30_v5.nc', + model_files = {} + model_files['u'] = ['TPXO9_atlas_v5/u_2n2_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_k1_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_k2_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_m2_tpxo9_atlas_30_v5.nc', @@ -527,11 +541,27 @@ def test_definition_TPXO9_glob(): 'TPXO9_atlas_v5/u_q1_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_s1_tpxo9_atlas_30_v5.nc', 'TPXO9_atlas_v5/u_s2_tpxo9_atlas_30_v5.nc'] + model_files['v'] = ['TPXO9_atlas_v5/v_2n2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_k1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_k2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_m2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_m4_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_mf_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_mm_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_mn4_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_ms4_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_n2_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_o1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_p1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_q1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_s1_tpxo9_atlas_30_v5.nc', + 'TPXO9_atlas_v5/v_s2_tpxo9_atlas_30_v5.nc'] # create temporary files for testing glob functionality - for model_file in model_files: - local = filepath.joinpath(model_file) - local.parent.mkdir(parents=True, exist_ok=True) - local.touch(exist_ok=True) + for t in ['u','v']: + for model_file in model_files[t]: + local = filepath.joinpath(model_file) + local.parent.mkdir(parents=True, exist_ok=True) + local.touch(exist_ok=True) # create temporary grid file local = filepath.joinpath(m.grid_file) local.touch(exist_ok=True) @@ -545,8 +575,9 @@ def test_definition_TPXO9_glob(): else: fid.write('{0}\t{1}\n'.format(attr,val)) # append glob strings for model file - glob_string = r'TPXO9_atlas_v5/u*.nc' - fid.write('{0}\t{1}\n'.format('model_file',glob_string)) + glob_string_u = r'TPXO9_atlas_v5/u*.nc' + glob_string_v = r'TPXO9_atlas_v5/v*.nc' + fid.write('{0}\t{1};{2}\n'.format('model_file',glob_string_u,glob_string_v)) fid.write('{0}\t{1}\n'.format('grid_file',m.grid_file)) fid.seek(0) # use model definition file as input @@ -555,8 +586,8 @@ def test_definition_TPXO9_glob(): assert getattr(model,attr) == getattr(m,attr) # verify that the model files match for key,val in model.model_file.items(): - assert (len(val) == len(model_files)) - for f in model_files: + assert (len(val) == len(model_files[key])) + for f in model_files[key]: assert pathlib.Path(filepath).joinpath(f) in model.model_file[key] # close the glob definition file fid.close()