Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • duncan.meacher/gwdatafind
  • duncanmmacleod/gwdatafind
  • computing/gwdatafind/client
3 results
Show changes
Commits on Source (11)
Showing with 415 additions and 310 deletions
include: include:
# -- Python ------------------------- # -- Python -------------------------
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/all@1 - component: $CI_SERVER_FQDN/computing/gitlab/components/python/all@2
inputs: inputs:
code_quality_analyzer: ruff code_quality_analyzer: ruff
install_extra: "test" install_extra: "test"
run_advanced_sast: true run_advanced_sast: true
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/type-checking@2
# -- Debian packaging --------------- # -- Debian packaging ---------------
...@@ -36,7 +37,7 @@ include: ...@@ -36,7 +37,7 @@ include:
# -- Red Hat packaging -------------- # -- Red Hat packaging --------------
- component: $CI_SERVER_FQDN/computing/gitlab/components/redhat/all@1 - component: $CI_SERVER_FQDN/computing/gitlab/components/redhat/all@2
inputs: inputs:
needs: [sdist] needs: [sdist]
redhat_versions: redhat_versions:
...@@ -50,3 +51,16 @@ include: ...@@ -50,3 +51,16 @@ include:
- component: $CI_SERVER_FQDN/computing/gitlab/components/sphinx/build@1 - component: $CI_SERVER_FQDN/computing/gitlab/components/sphinx/build@1
inputs: inputs:
requirements: ".[docs]" requirements: ".[docs]"
# -- customisations ------------------
redhat_test_el8:
before_script:
- !reference [redhat_test, before_script]
# install a newer version of pytest and friends on EL8
- dnf install -y -q python3-pip &&
python3 -m pip install
--upgrade-strategy=only-if-needed
"coverage[toml]==5.3"
"pytest==3.9.3"
...@@ -14,7 +14,7 @@ Build-Depends: ...@@ -14,7 +14,7 @@ Build-Depends:
python3-argparse-manpage, python3-argparse-manpage,
python3-igwn-auth-utils (>= 0.3.1), python3-igwn-auth-utils (>= 0.3.1),
python3-igwn-segments, python3-igwn-segments,
python3-pytest (>= 2.8.0), python3-pytest (>= 3.9.3),
python3-requests-mock, python3-requests-mock,
python3-setuptools, python3-setuptools,
......
...@@ -14,14 +14,14 @@ project = "gwdatafind" ...@@ -14,14 +14,14 @@ project = "gwdatafind"
copyright = "2018-2025, Cardiff University" copyright = "2018-2025, Cardiff University"
author = "Duncan Macleod" author = "Duncan Macleod"
release = gwdatafind.__version__ release = gwdatafind.__version__
version = re.split(r'[\w-]', gwdatafind.__version__)[0] version = re.split(r"[\w-]", gwdatafind.__version__)[0]
# -- config # -- config
source_suffix = '.rst' source_suffix = ".rst"
master_doc = 'index' master_doc = "index"
default_role = 'obj' default_role = "obj"
# -- theme # -- theme
......
...@@ -85,6 +85,6 @@ from igwn_auth_utils.requests import Session ...@@ -85,6 +85,6 @@ from igwn_auth_utils.requests import Session
from .ui import * from .ui import *
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
__credits__ = 'Scott Koranda <scott.koranda@ligo.org>' __credits__ = "Scott Koranda <scott.koranda@ligo.org>"
__version__ = '1.2.0' __version__ = "1.2.0"
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with GWDataFind. If not, see <http://www.gnu.org/licenses/>. # along with GWDataFind. If not, see <http://www.gnu.org/licenses/>.
"""Query a GWDataFind server for information. """Query a GWDataFind server for information."""
"""
import argparse import argparse
import re import re
...@@ -34,8 +33,8 @@ from .io import ( ...@@ -34,8 +33,8 @@ from .io import (
) )
from .utils import get_default_host from .utils import get_default_host
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
__credits__ = 'Scott Koranda, The LIGO Scientific Collaboration' __credits__ = "Scott Koranda, The LIGO Scientific Collaboration"
# -- command line parsing ----------------------------------------------------- # -- command line parsing -----------------------------------------------------
...@@ -44,7 +43,7 @@ class DataFindFormatter( ...@@ -44,7 +43,7 @@ class DataFindFormatter(
argparse.ArgumentDefaultsHelpFormatter, argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter, argparse.RawDescriptionHelpFormatter,
): ):
pass """Custom `argparse.Formatter` for GWDataFind."""
class DataFindArgumentParser(argparse.ArgumentParser): class DataFindArgumentParser(argparse.ArgumentParser):
...@@ -52,16 +51,15 @@ class DataFindArgumentParser(argparse.ArgumentParser): ...@@ -52,16 +51,15 @@ class DataFindArgumentParser(argparse.ArgumentParser):
Mainly to handle the legacy mutually-exclusive optional arguments... Mainly to handle the legacy mutually-exclusive optional arguments...
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Create a new `DataFindArgumentParser`. """Create a new `DataFindArgumentParser`."""
"""
kwargs.setdefault("formatter_class", DataFindFormatter) kwargs.setdefault("formatter_class", DataFindFormatter)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._optionals.title = "Optional arguments" self._optionals.title = "Optional arguments"
def parse_args(self, *args, **kwargs): def parse_args(self, *args, **kwargs):
"""Parse arguments with an extra sanity check. """Parse arguments with an extra sanity check."""
"""
args = super().parse_args(*args, **kwargs) args = super().parse_args(*args, **kwargs)
args.show_urls = not any((args.ping, args.show_observatories, args.show_urls = not any((args.ping, args.show_observatories,
args.show_types, args.show_times, args.show_types, args.show_times,
...@@ -96,12 +94,11 @@ class DataFindArgumentParser(argparse.ArgumentParser): ...@@ -96,12 +94,11 @@ class DataFindArgumentParser(argparse.ArgumentParser):
"--gps-end-time time all must be given when querying " "--gps-end-time time all must be given when querying "
"for file URLs") "for file URLs")
if namespace.gaps and not namespace.show_urls: if namespace.gaps and not namespace.show_urls:
self.error('-g/--gaps only allowed when querying for file URLs') self.error("-g/--gaps only allowed when querying for file URLs")
def command_line(): def command_line():
"""Build an `~argparse.ArgumentParser` for the `gwdatafind` CLI. """Build an `~argparse.ArgumentParser` for the `gwdatafind` CLI."""
"""
try: try:
defhost = get_default_host() defhost = get_default_host()
except ValueError: except ValueError:
...@@ -276,7 +273,7 @@ def command_line(): ...@@ -276,7 +273,7 @@ def command_line():
default="file", default="file",
help=( help=(
"return only URLs with a particular scheme or head " "return only URLs with a particular scheme or head "
"such as \"file\" or \"gsiftp\"" 'such as "file" or "gsiftp"'
), ),
) )
oargs.add_argument( oargs.add_argument(
...@@ -399,10 +396,10 @@ def show_times(args, out): ...@@ -399,10 +396,10 @@ def show_times(args, out):
host=args.server, host=args.server,
ext=args.extension, ext=args.extension,
) )
print('# seg\tstart \tstop \tduration', file=out) print("# seg\tstart \tstop \tduration", file=out)
for i, seg in enumerate(seglist): for i, seg in enumerate(seglist):
print( print(
f'{i}\t{seg[0]:10}\t{seg[1]:10}\t{abs(seg)}', f"{i}\t{seg[0]:10}\t{seg[1]:10}\t{abs(seg)}",
file=out, file=out,
) )
...@@ -427,7 +424,7 @@ def latest(args, out): ...@@ -427,7 +424,7 @@ def latest(args, out):
args.observatory, args.observatory,
args.type, args.type,
urltype=args.url_type, urltype=args.url_type,
on_missing='warn', on_missing="warn",
host=args.server, host=args.server,
ext=args.extension, ext=args.extension,
) )
...@@ -453,7 +450,7 @@ def filename(args, out): ...@@ -453,7 +450,7 @@ def filename(args, out):
cache = ui.find_url( cache = ui.find_url(
args.filename, args.filename,
urltype=args.url_type, urltype=args.url_type,
on_missing='warn', on_missing="warn",
host=args.server, host=args.server,
) )
return postprocess_cache(cache, args, out) return postprocess_cache(cache, args, out)
...@@ -483,7 +480,7 @@ def show_urls(args, out): ...@@ -483,7 +480,7 @@ def show_urls(args, out):
match=args.match, match=args.match,
urltype=args.url_type, urltype=args.url_type,
host=args.server, host=args.server,
on_gaps='ignore', on_gaps="ignore",
ext=args.extension, ext=args.extension,
) )
return postprocess_cache(cache, args, out) return postprocess_cache(cache, args, out)
...@@ -496,10 +493,10 @@ def postprocess_cache(urls, args, out): ...@@ -496,10 +493,10 @@ def postprocess_cache(urls, args, out):
in the requested format, then prints gaps to stderr if requested. in the requested format, then prints gaps to stderr if requested.
""" """
# if searching for SFTs replace '.gwf' file suffix with '.sft' # if searching for SFTs replace '.gwf' file suffix with '.sft'
if re.search(r'_\d+SFT(\Z|_)', str(args.type)): if re.search(r"_\d+SFT(\Z|_)", str(args.type)):
gwfreg = re.compile(r'\.gwf\Z') gwfreg = re.compile(r"\.gwf\Z")
for i, url in enumerate(urls): for i, url in enumerate(urls):
urls[i] = gwfreg.sub('.sft', url) urls[i] = gwfreg.sub(".sft", url)
cache = lal_cache(urls) cache = lal_cache(urls)
...@@ -524,15 +521,14 @@ def postprocess_cache(urls, args, out): ...@@ -524,15 +521,14 @@ def postprocess_cache(urls, args, out):
# -- CLI ---------------------------------------------------------------------- # -- CLI ----------------------------------------------------------------------
def main(args=None): def main(args=None):
"""Run the thing. """Run the thing."""
"""
# parse command line # parse command line
parser = command_line() parser = command_line()
opts = parser.parse_args(args=args) opts = parser.parse_args(args=args)
# open output # open output
if opts.output_file: if opts.output_file:
out = open(opts.output_file, 'w') out = open(opts.output_file, "w")
else: else:
out = sys.stdout out = sys.stdout
...@@ -557,5 +553,5 @@ def main(args=None): ...@@ -557,5 +553,5 @@ def main(args=None):
out.close() out.close()
if __name__ == '__main__': # pragma: no-cover if __name__ == "__main__": # pragma: no-cover
sys.exit(main()) sys.exit(main())
...@@ -23,7 +23,7 @@ to execute various GWDataFind queries. ...@@ -23,7 +23,7 @@ to execute various GWDataFind queries.
from functools import wraps from functools import wraps
from os.path import basename from os.path import basename
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
DEFAULT_EXT = "gwf" DEFAULT_EXT = "gwf"
DEFAULT_SERVICE_PREFIX = "LDR/services/data/v1" DEFAULT_SERVICE_PREFIX = "LDR/services/data/v1"
...@@ -45,22 +45,19 @@ def _prefix(func): ...@@ -45,22 +45,19 @@ def _prefix(func):
@_prefix @_prefix
def ping_path(ext=DEFAULT_EXT): def ping_path(ext=DEFAULT_EXT):
"""Return the API path to ping the server. """Return the API path to ping the server."""
"""
return f"{ext}/H/R/1,2" return f"{ext}/H/R/1,2"
@_prefix @_prefix
def find_observatories_path(ext=DEFAULT_EXT): def find_observatories_path(ext=DEFAULT_EXT):
"""Return the API path to query for all observatories. """Return the API path to query for all observatories."""
"""
return f"{ext}.json" return f"{ext}.json"
@_prefix @_prefix
def find_types_path(site=None, ext=DEFAULT_EXT): def find_types_path(site=None, ext=DEFAULT_EXT):
"""Return the API path to query for datasets for one or all sites. """Return the API path to query for datasets for one or all sites."""
"""
if site: if site:
return f"{ext}/{site}.json" return f"{ext}/{site}.json"
return f"{ext}/all.json" return f"{ext}/all.json"
...@@ -68,8 +65,7 @@ def find_types_path(site=None, ext=DEFAULT_EXT): ...@@ -68,8 +65,7 @@ def find_types_path(site=None, ext=DEFAULT_EXT):
@_prefix @_prefix
def find_times_path(site, frametype, start, end, ext=DEFAULT_EXT): def find_times_path(site, frametype, start, end, ext=DEFAULT_EXT):
"""Return the API path to query for data availability segments. """Return the API path to query for data availability segments."""
"""
if start is None and end is None: if start is None and end is None:
return f"{ext}/{site}/{frametype}/segments.json" return f"{ext}/{site}/{frametype}/segments.json"
return f"{ext}/{site}/{frametype}/segments/{start},{end}.json" return f"{ext}/{site}/{frametype}/segments/{start},{end}.json"
...@@ -77,8 +73,7 @@ def find_times_path(site, frametype, start, end, ext=DEFAULT_EXT): ...@@ -77,8 +73,7 @@ def find_times_path(site, frametype, start, end, ext=DEFAULT_EXT):
@_prefix @_prefix
def find_url_path(framefile): def find_url_path(framefile):
"""Return the API path to query for the URL of a specific filename. """Return the API path to query for the URL of a specific filename."""
"""
filename = basename(framefile) filename = basename(framefile)
ext = filename.split(".", 1)[1] ext = filename.split(".", 1)[1]
site, frametype, _ = filename.split("-", 2) site, frametype, _ = filename.split("-", 2)
...@@ -87,8 +82,7 @@ def find_url_path(framefile): ...@@ -87,8 +82,7 @@ def find_url_path(framefile):
@_prefix @_prefix
def find_latest_path(site, frametype, urltype, ext=DEFAULT_EXT): def find_latest_path(site, frametype, urltype, ext=DEFAULT_EXT):
"""Return the API path to query for the latest file in a dataset. """Return the API path to query for the latest file in a dataset."""
"""
stub = f"{ext}/{site}/{frametype}/latest" stub = f"{ext}/{site}/{frametype}/latest"
if urltype: if urltype:
return f"{stub}/{urltype}.json" return f"{stub}/{urltype}.json"
...@@ -105,8 +99,7 @@ def find_urls_path( ...@@ -105,8 +99,7 @@ def find_urls_path(
match=None, match=None,
ext=DEFAULT_EXT, ext=DEFAULT_EXT,
): ):
"""Return the API path to query for all URLs for a dataset in an interval. """Return the API path to query for all URLs for a dataset in an interval."""
"""
stub = f"{ext}/{site}/{frametype}/{start},{end}" stub = f"{ext}/{site}/{frametype}/{start},{end}"
if urltype: if urltype:
path = f"{stub}/{urltype}.json" path = f"{stub}/{urltype}.json"
......
# Copyright (C) Cardiff University (2018-2022)
#
# This file is part of GWDataFind.
#
# GWDataFind is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GWDataFind is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GWDataFind. If not, see <http://www.gnu.org/licenses/>.
"""Pytest hooks for gwdatafind.
"""
import warnings
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>'
# always present warnings during testing
warnings.simplefilter('always')
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with GWDataFind. If not, see <http://www.gnu.org/licenses/>. # along with GWDataFind. If not, see <http://www.gnu.org/licenses/>.
"""I/O (mainly O) routines for GWDataFind. """I/O (mainly O) routines for GWDataFind."""
"""
import os.path import os.path
from collections import namedtuple from collections import namedtuple
...@@ -25,17 +24,19 @@ from urllib.parse import urlparse ...@@ -25,17 +24,19 @@ from urllib.parse import urlparse
from .utils import filename_metadata from .utils import filename_metadata
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
class LalCacheEntry( class LalCacheEntry(
namedtuple('CacheEntry', ('obs', 'tag', 'segment', 'url')), namedtuple("CacheEntry", ("obs", "tag", "segment", "url")),
): ):
"""Simplified version of `lal.utils.CacheEntry`. """Simplified version of `lal.utils.CacheEntry`.
This is provided so that we don't have to depend on lalsuite. This is provided so that we don't have to depend on lalsuite.
""" """
def __str__(self): def __str__(self):
"""Return a `str` representation of this `LalCacheEntry`."""
seg = self.segment seg = self.segment
return " ".join(map(str, ( return " ".join(map(str, (
self.obs, self.obs,
...@@ -47,8 +48,7 @@ class LalCacheEntry( ...@@ -47,8 +48,7 @@ class LalCacheEntry(
@classmethod @classmethod
def from_url(cls, url, **kwargs): def from_url(cls, url, **kwargs):
"""Create a new `LalCacheEntry` from a URL that follows LIGO-T050017. """Create a new `LalCacheEntry` from a URL that follows LIGO-T050017."""
"""
obs, tag, seg = filename_metadata(url) obs, tag, seg = filename_metadata(url)
return cls(obs, tag, seg, url) return cls(obs, tag, seg, url)
...@@ -64,8 +64,8 @@ def lal_cache(urls): ...@@ -64,8 +64,8 @@ def lal_cache(urls):
class OmegaCacheEntry(namedtuple( class OmegaCacheEntry(namedtuple(
'OmegaCacheEntry', "OmegaCacheEntry",
('obs', 'tag', 'segment', 'duration', 'url') ("obs", "tag", "segment", "duration", "url")
)): )):
"""CacheEntry for an omega-style cache. """CacheEntry for an omega-style cache.
...@@ -74,7 +74,9 @@ class OmegaCacheEntry(namedtuple( ...@@ -74,7 +74,9 @@ class OmegaCacheEntry(namedtuple(
<obs> <tag> <dir-start> <dir-end> <file-duration> <directory> <obs> <tag> <dir-start> <dir-end> <file-duration> <directory>
""" """
def __str__(self): def __str__(self):
"""Return a `str` representation of this `OmegaCacheEntry`."""
return " ".join(map(str, ( return " ".join(map(str, (
self.obs, self.obs,
self.tag, self.tag,
...@@ -97,7 +99,7 @@ def omega_cache(cache): ...@@ -97,7 +99,7 @@ def omega_cache(cache):
wentry = None wentry = None
for entry in sorted( for entry in sorted(
cache, cache,
key=attrgetter('obs', 'tag', 'segment'), key=attrgetter("obs", "tag", "segment"),
): ):
dir_ = os.path.dirname(entry.url) dir_ = os.path.dirname(entry.url)
......
...@@ -15,14 +15,6 @@ ...@@ -15,14 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with GWDataFind. If not, see <http://www.gnu.org/licenses/>. # along with GWDataFind. If not, see <http://www.gnu.org/licenses/>.
"""Tests for :mod:`gwdatafind`. """Test :mod:`gwdatafind`."""
"""
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
import pytest
if pytest.__version__ < '3.0':
yield_fixture = pytest.yield_fixture
else:
yield_fixture = pytest.fixture
# Copyright (C) Cardiff University (2018-2022)
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Test utilities.
"""
import os
import tempfile
from . import yield_fixture
@yield_fixture
def tmpname():
"""Return a temporary file name, cleaning up after the method returns.
"""
name = tempfile.mktemp()
open(name, 'w').close()
try:
yield name
finally:
if os.path.isfile(name):
os.remove(name)
...@@ -23,56 +23,63 @@ from the v1 API for gwdatfind_server. ...@@ -23,56 +23,63 @@ from the v1 API for gwdatfind_server.
import pytest import pytest
from .. import api from gwdatafind import api
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
def test_ping_path(): def test_ping_path():
"""Test `ping_path()`."""
assert api.ping_path() == "LDR/services/data/v1/gwf/H/R/1,2" assert api.ping_path() == "LDR/services/data/v1/gwf/H/R/1,2"
def test_find_observatories_path(): def test_find_observatories_path():
"""Test `find_observatories_path()`."""
assert api.find_observatories_path() == "LDR/services/data/v1/gwf.json" assert api.find_observatories_path() == "LDR/services/data/v1/gwf.json"
@pytest.mark.parametrize(("site", "result"), ( @pytest.mark.parametrize(("site", "result"), [
(None, "LDR/services/data/v1/gwf/all.json"), (None, "LDR/services/data/v1/gwf/all.json"),
("X", "LDR/services/data/v1/gwf/X.json"), ("X", "LDR/services/data/v1/gwf/X.json"),
("XY", "LDR/services/data/v1/gwf/XY.json"), ("XY", "LDR/services/data/v1/gwf/XY.json"),
)) ])
def test_find_types_path(site, result): def test_find_types_path(site, result):
"""Test `find_types_path()`."""
assert api.find_types_path(site) == result assert api.find_types_path(site) == result
def test_find_times_path(): def test_find_times_path():
"""Test `find_times_path()`."""
assert api.find_times_path("X", "TEST", 0, 1) == ( assert api.find_times_path("X", "TEST", 0, 1) == (
"LDR/services/data/v1/gwf/X/TEST/segments/0,1.json" "LDR/services/data/v1/gwf/X/TEST/segments/0,1.json"
) )
def test_find_url_path(): def test_find_url_path():
"""Test `find_url_path()`."""
assert api.find_url_path("/data/X-TEST-0-1.gwf") == ( assert api.find_url_path("/data/X-TEST-0-1.gwf") == (
"LDR/services/data/v1/gwf/X/TEST/X-TEST-0-1.gwf.json" "LDR/services/data/v1/gwf/X/TEST/X-TEST-0-1.gwf.json"
) )
@pytest.mark.parametrize(("urltype", "result"), ( @pytest.mark.parametrize(("urltype", "result"), [
(None, "LDR/services/data/v1/gwf/X/TEST/latest.json"), (None, "LDR/services/data/v1/gwf/X/TEST/latest.json"),
("file", "LDR/services/data/v1/gwf/X/TEST/latest/file.json"), ("file", "LDR/services/data/v1/gwf/X/TEST/latest/file.json"),
)) ])
def test_find_latest_path(urltype, result): def test_find_latest_path(urltype, result):
"""Test `find_latest_path()`."""
assert api.find_latest_path("X", "TEST", urltype) == result assert api.find_latest_path("X", "TEST", urltype) == result
@pytest.mark.parametrize(("urltype", "match", "result"), ( @pytest.mark.parametrize(("urltype", "match", "result"), [
(None, None, "LDR/services/data/v1/gwf/X/TEST/0,1.json"), (None, None, "LDR/services/data/v1/gwf/X/TEST/0,1.json"),
("gsiftp", None, "LDR/services/data/v1/gwf/X/TEST/0,1/gsiftp.json"), ("gsiftp", None, "LDR/services/data/v1/gwf/X/TEST/0,1/gsiftp.json"),
(None, "test", "LDR/services/data/v1/gwf/X/TEST/0,1.json?match=test"), (None, "test", "LDR/services/data/v1/gwf/X/TEST/0,1.json?match=test"),
("file", "test", ("file", "test",
"LDR/services/data/v1/gwf/X/TEST/0,1/file.json?match=test"), "LDR/services/data/v1/gwf/X/TEST/0,1/file.json?match=test"),
)) ])
def find_urls_path(urltype, match, result): def find_urls_path(urltype, match, result):
"""Test `find_urls_path()`."""
assert api.find_urls_path( assert api.find_urls_path(
"X", "X",
"TEST", "TEST",
......
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with GWDataFind. If not, see <http://www.gnu.org/licenses/>. # along with GWDataFind. If not, see <http://www.gnu.org/licenses/>.
"""Tests for :mod:`gwdatafind.__main__` (the CLI). """Tests for :mod:`gwdatafind.__main__` (the CLI)."""
"""
import argparse import argparse
import os import os
...@@ -26,17 +25,17 @@ from unittest import mock ...@@ -26,17 +25,17 @@ from unittest import mock
import pytest import pytest
from igwn_segments import segment from igwn_segments import segment
from .. import __main__ as main from gwdatafind import __main__ as main
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
URLS = [ URLS = [
'file:///test/X-test-0-1.gwf', "file:///test/X-test-0-1.gwf",
'file:///test/X-test-1-1.gwf', "file:///test/X-test-1-1.gwf",
'file:///test2/X-test-2-1.gwf', "file:///test2/X-test-2-1.gwf",
'file:///test2/X-test-7-4.gwf', "file:///test2/X-test-7-4.gwf",
'file:///test/X-test-0-1.h5', "file:///test/X-test-0-1.h5",
'file:///test/X-test-1-1.h5', "file:///test/X-test-1-1.h5",
] ]
GWF_URLS = [url for url in URLS if url.endswith(".gwf")] GWF_URLS = [url for url in URLS if url.endswith(".gwf")]
GWF_OUTPUT_URLS = """ GWF_OUTPUT_URLS = """
...@@ -65,61 +64,69 @@ X test 7 11 4 file:///test2 ...@@ -65,61 +64,69 @@ X test 7 11 4 file:///test2
GAPS = [(3, 7)] GAPS = [(3, 7)]
@mock.patch.dict(os.environ, {'GWDATAFIND_SERVER': 'something'}) @mock.patch.dict(os.environ, {"GWDATAFIND_SERVER": "something"})
def test_command_line(): def test_command_line():
"""Test `command_line()`."""
parser = main.command_line() parser = main.command_line()
assert isinstance(parser, argparse.ArgumentParser) assert isinstance(parser, argparse.ArgumentParser)
assert parser.description == main.__doc__ assert parser.description == main.__doc__
for query in ('ping', 'show_observatories', 'show_types', 'show_times', for query in ("ping", "show_observatories", "show_types", "show_times",
'filename', 'latest'): "filename", "latest"):
assert not parser.get_default(query) assert not parser.get_default(query)
assert parser.get_default('server') == os.getenv('GWDATAFIND_SERVER') assert parser.get_default("server") == os.getenv("GWDATAFIND_SERVER")
assert parser.get_default('url_type') == 'file' assert parser.get_default("url_type") == "file"
assert parser.get_default('gaps') is False assert parser.get_default("gaps") is False
# test parsing and types # test parsing and types
args = parser.parse_args([ args = parser.parse_args([
'-o', 'X', '-t', 'test', '--gps-start-time', '0', '-e', '1', "-o", "X", "-t", "test", "--gps-start-time", "0", "-e", "1",
]) ])
assert args.gpsstart == 0. assert args.gpsstart == 0.
assert args.gpsend == 1. assert args.gpsend == 1.
assert args.server == 'something' assert args.server == "something"
@mock.patch.dict('os.environ', clear=True) @mock.patch.dict("os.environ", clear=True)
@pytest.mark.parametrize('defserv', (None, 'test.datafind.com:443')) @pytest.mark.parametrize("defserv", [None, "test.datafind.com:443"])
def test_command_line_server(defserv): def test_command_line_server(defserv):
"""Test default setting for ``-r/--server``."""
if defserv: if defserv:
os.environ['GWDATAFIND_SERVER'] = defserv os.environ["GWDATAFIND_SERVER"] = defserv
parser = main.command_line() parser = main.command_line()
serveract = [act for act in parser._actions if act.dest == 'server'][0] serveract = next(act for act in parser._actions if act.dest == "server")
assert serveract.required is (not defserv) assert serveract.required is (not defserv)
@mock.patch.dict(os.environ, {'GWDATAFIND_SERVER': 'something'}) @mock.patch.dict(os.environ, {"GWDATAFIND_SERVER": "something"})
def test_sanity_check_pass(): def test_sanity_check_pass():
"""Test `DataFindArgumentParser.sanity_check()`."""
parser = main.command_line() parser = main.command_line()
parser.parse_args(['-o', 'X', '-t', 'test', '-s', '0', '-e', '1']) parser.parse_args(["-o", "X", "-t", "test", "-s", "0", "-e", "1"])
@mock.patch.dict(os.environ, {'GWDATAFIND_SERVER': 'something'}) @mock.patch.dict(os.environ, {"GWDATAFIND_SERVER": "something"})
@pytest.mark.parametrize('clargs', [ @pytest.mark.parametrize("clargs", [
('--show-times', '--observatory', 'X'), ("--show-times", "--observatory", "X"),
('--show-times', '--type', 'test'), ("--show-times", "--type", "test"),
('--type', 'test', '--observatory', 'X', '--gps-start-time', '1'), ("--type", "test", "--observatory", "X", "--gps-start-time", "1"),
('--gaps', '--show-observatories'), ("--gaps", "--show-observatories"),
]) ])
def test_sanity_check_fail(clargs): def test_sanity_check_fail(clargs):
"""Test `DataFindArgumentParser.sanity_check()` error reporting."""
parser = main.command_line() parser = main.command_line()
with pytest.raises(SystemExit): with pytest.raises(
SystemExit,
match="2",
):
parser.parse_args(clargs) parser.parse_args(clargs)
@mock.patch('gwdatafind.ui.ping') @mock.patch("gwdatafind.ui.ping")
def test_ping(mping): def test_ping(mping):
"""Test `ping()."""
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
extension='gwf', extension="gwf",
) )
out = StringIO() out = StringIO()
main.ping(args, out) main.ping(args, out)
...@@ -129,16 +136,17 @@ def test_ping(mping): ...@@ -129,16 +136,17 @@ def test_ping(mping):
) )
out.seek(0) out.seek(0)
assert out.read().rstrip() == ( assert out.read().rstrip() == (
'LDRDataFindServer at test.datafind.com:443 is alive') "LDRDataFindServer at test.datafind.com:443 is alive")
@mock.patch('gwdatafind.ui.find_observatories') @mock.patch("gwdatafind.ui.find_observatories")
def test_show_observatories(mfindobs): def test_show_observatories(mfindobs):
mfindobs.return_value = ['A', 'B', 'C'] """Test `show_observatories()."""
mfindobs.return_value = ["A", "B", "C"]
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
extension='gwf', extension="gwf",
match='test', match="test",
) )
out = StringIO() out = StringIO()
main.show_observatories(args, out) main.show_observatories(args, out)
...@@ -148,17 +156,18 @@ def test_show_observatories(mfindobs): ...@@ -148,17 +156,18 @@ def test_show_observatories(mfindobs):
match=args.match, match=args.match,
ext=args.extension, ext=args.extension,
) )
assert list(map(str.rstrip, out.readlines())) == ['A', 'B', 'C'] assert list(map(str.rstrip, out.readlines())) == ["A", "B", "C"]
@mock.patch('gwdatafind.ui.find_types') @mock.patch("gwdatafind.ui.find_types")
def test_show_types(mfindtypes): def test_show_types(mfindtypes):
mfindtypes.return_value = ['A', 'B', 'C'] """Test `show_types()."""
mfindtypes.return_value = ["A", "B", "C"]
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
extension='gwf', extension="gwf",
observatory='X', observatory="X",
match='test', match="test",
) )
out = StringIO() out = StringIO()
main.show_types(args, out) main.show_types(args, out)
...@@ -169,17 +178,18 @@ def test_show_types(mfindtypes): ...@@ -169,17 +178,18 @@ def test_show_types(mfindtypes):
site=args.observatory, site=args.observatory,
ext=args.extension, ext=args.extension,
) )
assert list(map(str.rstrip, out.readlines())) == ['A', 'B', 'C'] assert list(map(str.rstrip, out.readlines())) == ["A", "B", "C"]
@mock.patch('gwdatafind.ui.find_times') @mock.patch("gwdatafind.ui.find_times")
def test_show_times(mfindtimes): def test_show_times(mfindtimes):
"""Test `show_times()."""
mfindtimes.return_value = [segment(0, 1), segment(1, 2), segment(3, 4)] mfindtimes.return_value = [segment(0, 1), segment(1, 2), segment(3, 4)]
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
extension='gwf', extension="gwf",
observatory='X', observatory="X",
type='test', type="test",
gpsstart=0, gpsstart=0,
gpsend=10, gpsend=10,
) )
...@@ -199,15 +209,16 @@ def test_show_times(mfindtimes): ...@@ -199,15 +209,16 @@ def test_show_times(mfindtimes):
assert line.split() == list(map(str, (i, seg[0], seg[1], abs(seg)))) assert line.split() == list(map(str, (i, seg[0], seg[1], abs(seg))))
@mock.patch('gwdatafind.ui.find_latest') @mock.patch("gwdatafind.ui.find_latest")
def test_latest(mlatest): def test_latest(mlatest):
mlatest.return_value = ['file:///test/X-test-0-10.gwf'] """Test `latest()`."""
mlatest.return_value = ["file:///test/X-test-0-10.gwf"]
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
extension='gwf', extension="gwf",
observatory='X', observatory="X",
type='test', type="test",
url_type='file', url_type="file",
format="urls", format="urls",
gaps=None, gaps=None,
) )
...@@ -217,7 +228,7 @@ def test_latest(mlatest): ...@@ -217,7 +228,7 @@ def test_latest(mlatest):
args.observatory, args.observatory,
args.type, args.type,
urltype=args.url_type, urltype=args.url_type,
on_missing='warn', on_missing="warn",
host=args.server, host=args.server,
ext=args.extension, ext=args.extension,
) )
...@@ -225,13 +236,14 @@ def test_latest(mlatest): ...@@ -225,13 +236,14 @@ def test_latest(mlatest):
assert out.read().rstrip() == mlatest.return_value[0] assert out.read().rstrip() == mlatest.return_value[0]
@mock.patch('gwdatafind.ui.find_url') @mock.patch("gwdatafind.ui.find_url")
def test_filename(mfindurl): def test_filename(mfindurl):
mfindurl.return_value = ['file:///test/X-test-0-10.gwf'] """Test `filename()`."""
mfindurl.return_value = ["file:///test/X-test-0-10.gwf"]
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
filename='X-test-0-10.gwf', filename="X-test-0-10.gwf",
url_type='file', url_type="file",
type=None, type=None,
format="urls", format="urls",
gaps=None, gaps=None,
...@@ -241,29 +253,30 @@ def test_filename(mfindurl): ...@@ -241,29 +253,30 @@ def test_filename(mfindurl):
mfindurl.assert_called_with( mfindurl.assert_called_with(
args.filename, args.filename,
urltype=args.url_type, urltype=args.url_type,
on_missing='warn', on_missing="warn",
host=args.server, host=args.server,
) )
out.seek(0) out.seek(0)
assert out.read().rstrip() == mfindurl.return_value[0] assert out.read().rstrip() == mfindurl.return_value[0]
@mock.patch('gwdatafind.ui.find_urls') @mock.patch("gwdatafind.ui.find_urls")
@pytest.mark.parametrize("ext", [ @pytest.mark.parametrize("ext", [
"gwf", "gwf",
"h5", "h5",
]) ])
def test_show_urls(mfindurls, ext): def test_show_urls(mfindurls, ext):
"""Test `show_urls()`."""
urls = [x for x in URLS if x.endswith(f".{ext}")] urls = [x for x in URLS if x.endswith(f".{ext}")]
mfindurls.return_value = urls mfindurls.return_value = urls
args = argparse.Namespace( args = argparse.Namespace(
server='test.datafind.com:443', server="test.datafind.com:443",
extension=ext, extension=ext,
observatory='X', observatory="X",
type='test', type="test",
gpsstart=0, gpsstart=0,
gpsend=10, gpsend=10,
url_type='file', url_type="file",
match=None, match=None,
format="urls", format="urls",
gaps=None, gaps=None,
...@@ -277,7 +290,7 @@ def test_show_urls(mfindurls, ext): ...@@ -277,7 +290,7 @@ def test_show_urls(mfindurls, ext):
args.gpsend, args.gpsend,
match=args.match, match=args.match,
urltype=args.url_type, urltype=args.url_type,
on_gaps='ignore', on_gaps="ignore",
ext=ext, ext=ext,
host=args.server, host=args.server,
) )
...@@ -285,13 +298,14 @@ def test_show_urls(mfindurls, ext): ...@@ -285,13 +298,14 @@ def test_show_urls(mfindurls, ext):
assert list(map(str.rstrip, out.readlines())) == urls assert list(map(str.rstrip, out.readlines())) == urls
@pytest.mark.parametrize('fmt,result', [ @pytest.mark.parametrize(("fmt", "result"), [
("urls", GWF_OUTPUT_URLS), ("urls", GWF_OUTPUT_URLS),
("lal", GWF_OUTPUT_LAL_CACHE), ("lal", GWF_OUTPUT_LAL_CACHE),
("names", GWF_OUTPUT_NAMES_ONLY), ("names", GWF_OUTPUT_NAMES_ONLY),
("omega", GWF_OUTPUT_OMEGA_CACHE), ("omega", GWF_OUTPUT_OMEGA_CACHE),
]) ])
def test_postprocess_cache_format(fmt, result): def test_postprocess_cache_format(fmt, result):
"""Test `postprocess_cache` with ``--format``."""
# create namespace for parsing # create namespace for parsing
args = argparse.Namespace( args = argparse.Namespace(
type=None, type=None,
...@@ -309,18 +323,20 @@ def test_postprocess_cache_format(fmt, result): ...@@ -309,18 +323,20 @@ def test_postprocess_cache_format(fmt, result):
def test_postprocess_cache_sft(): def test_postprocess_cache_sft():
"""Test `postprocess_cache` for SFTs."""
args = argparse.Namespace( args = argparse.Namespace(
type='TEST_1800SFT', type="TEST_1800SFT",
format=None, format=None,
gaps=None, gaps=None,
) )
out = StringIO() out = StringIO()
main.postprocess_cache(GWF_URLS, args, out) main.postprocess_cache(GWF_URLS, args, out)
out.seek(0) out.seek(0)
assert out.read() == GWF_OUTPUT_URLS.replace('.gwf', '.sft') assert out.read() == GWF_OUTPUT_URLS.replace(".gwf", ".sft")
def test_postprocess_cache_gaps(capsys): def test_postprocess_cache_gaps(capsys):
"""Test `postprocess_cache()`."""
args = argparse.Namespace( args = argparse.Namespace(
gpsstart=0, gpsstart=0,
gpsend=10, gpsend=10,
...@@ -339,22 +355,25 @@ def test_postprocess_cache_gaps(capsys): ...@@ -339,22 +355,25 @@ def test_postprocess_cache_gaps(capsys):
assert main.postprocess_cache(URLS, args, out) == 2 assert main.postprocess_cache(URLS, args, out) == 2
@mock.patch.dict(os.environ, {'GWDATAFIND_SERVER': 'something'}) @mock.patch.dict(os.environ, {"GWDATAFIND_SERVER": "something"})
@pytest.mark.parametrize('args,patch', [ @pytest.mark.parametrize(("args", "patch"), [
(['--ping'], 'ping'), (["--ping"], "ping"),
(['--show-observatories'], 'show_observatories'), (["--show-observatories"], "show_observatories"),
(['--show-types'], 'show_types'), (["--show-types"], "show_types"),
(['--show-times', '-o', 'X', '-t', 'test'], 'show_times'), (["--show-times", "-o", "X", "-t", "test"], "show_times"),
(['--latest', '-o', 'X', '-t', 'test'], 'latest'), (["--latest", "-o", "X", "-t", "test"], "latest"),
(['--filename', 'X-test-0-1.gwf'], 'filename'), (["--filename", "X-test-0-1.gwf"], "filename"),
(['-o', 'X', '-t', 'test', '-s', '0', '-e', '10'], 'show_urls'), (["-o", "X", "-t", "test", "-s", "0", "-e", "10"], "show_urls"),
]) ])
def test_main(args, patch, tmpname): def test_main(args, patch, tmp_path):
"""Test `main()`."""
outfile = tmp_path / "out"
with mock.patch(f"gwdatafind.__main__.{patch}") as mocked: with mock.patch(f"gwdatafind.__main__.{patch}") as mocked:
main.main(args) main.main(args)
assert mocked.call_count == 1 assert mocked.call_count == 1
# call again with output file # call again with output file
args.extend(('--output-file', tmpname)) args.extend(("--output-file", str(outfile)))
with mock.patch(f"gwdatafind.__main__.{patch}") as mocked: with mock.patch(f"gwdatafind.__main__.{patch}") as mocked:
main.main(args) main.main(args)
assert mocked.call_count == 1 assert mocked.call_count == 1
...@@ -15,20 +15,26 @@ ...@@ -15,20 +15,26 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with GWDataFind. If not, see <http://www.gnu.org/licenses/>. # along with GWDataFind. If not, see <http://www.gnu.org/licenses/>.
"""Test :mod:`gwdatafind.ui`."""
import warnings import warnings
from contextlib import contextmanager
from functools import partial from functools import partial
from math import (
ceil,
floor,
)
from unittest import mock from unittest import mock
import igwn_segments as segments import igwn_segments as segments
import pytest import pytest
from .. import ( from gwdatafind import (
api, api,
ui, ui,
) )
from . import yield_fixture
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
TEST_SERVER = "test.datafind.org" TEST_SERVER = "test.datafind.org"
TEST_URL_BASE = f"https://{TEST_SERVER}" TEST_URL_BASE = f"https://{TEST_SERVER}"
...@@ -47,12 +53,22 @@ TEST_DATA = { ...@@ -47,12 +53,22 @@ TEST_DATA = {
} }
@contextmanager
def no_warning():
"""Context manager to ensure no warnings are emitted."""
with warnings.catch_warnings() as ctx:
warnings.simplefilter("error")
yield ctx
def _url(suffix): def _url(suffix):
"""Return a fully-qualified URL with the given suffix."""
return f"{TEST_URL_BASE}/{suffix}" return f"{TEST_URL_BASE}/{suffix}"
@yield_fixture(autouse=True) @pytest.fixture(autouse=True)
def gwdatafind_server_env(): def gwdatafind_server_env():
"""Patch `os.environ` with our value for ``GWDATAFIND_SERVER``."""
with mock.patch.dict( with mock.patch.dict(
"os.environ", "os.environ",
{"GWDATAFIND_SERVER": TEST_SERVER}, {"GWDATAFIND_SERVER": TEST_SERVER},
...@@ -60,45 +76,77 @@ def gwdatafind_server_env(): ...@@ -60,45 +76,77 @@ def gwdatafind_server_env():
yield yield
@yield_fixture(autouse=True, scope="module") @pytest.fixture(autouse=True, scope="module")
def noauth(): def noauth():
"""Force the underlying _get() function to use no authentication. """Force the underlying _get() function to use no authentication.
So that the tests don't fall over if the test runner has bad creds. So that the tests don't fall over if the test runner has bad creds.
""" """
_get_noauth = partial(ui._get, cert=False, token=False) with mock.patch(
with mock.patch("gwdatafind.ui._get", _get_noauth): "gwdatafind.ui._get",
partial(ui._get, cert=False, token=False), # noqa: SLF001
):
yield yield
@pytest.mark.parametrize(("in_", "url"), ( @pytest.mark.parametrize(("in_", "url"), [
# no scheme and no port, default to https # no scheme and no port, default to https
("datafind.example.com", "https://datafind.example.com"), pytest.param(
"datafind.example.com",
"https://datafind.example.com",
id="scheme-default",
),
# scheme specified, do nothing # scheme specified, do nothing
("test://datafind.example.com", "test://datafind.example.com"), pytest.param(
("test://datafind.example.com:1234", "test://datafind.example.com:1234"), "test://datafind.example.com",
("https://datafind.example.com:80", "https://datafind.example.com:80"), "test://datafind.example.com",
id="scheme-noop",
),
pytest.param(
"test://datafind.example.com:1234",
"test://datafind.example.com:1234",
id="port-noop",
),
pytest.param(
"https://datafind.example.com:80",
"https://datafind.example.com:80",
id="scheme-port-noop",
),
# no scheme and port 80, use http # no scheme and port 80, use http
("datafind.example.com:80", "http://datafind.example.com:80"), pytest.param(
"datafind.example.com:80",
"http://datafind.example.com:80",
id="scheme-port-http",
),
# no scheme and port != 80, use https # no scheme and port != 80, use https
("datafind.example.com:443", "https://datafind.example.com:443"), pytest.param(
"datafind.example.com:443",
"https://datafind.example.com:443",
id="scheme-port-https",
),
# default host # default host
(None, TEST_URL_BASE), pytest.param(
)) None,
TEST_URL_BASE,
),
])
def test_url_scheme_handling(in_, url): def test_url_scheme_handling(in_, url):
assert ui._url(in_, lambda: "test") == f"{url}/test" """Test URL scheme handling in `_url()`."""
assert ui._url(in_, lambda: "test") == f"{url}/test" # noqa: SLF001
def test_ping(requests_mock): def test_ping(requests_mock):
"""Test `ping()`."""
requests_mock.get(_url(api.ping_path()), status_code=200) requests_mock.get(_url(api.ping_path()), status_code=200)
ui.ping() ui.ping()
@pytest.mark.parametrize(("match", "result"), ( @pytest.mark.parametrize(("match", "result"), [
(None, ("A", "B", "C")), pytest.param(None, ("A", "B", "C"), id="all"),
("[AB]", ("A", "B")), pytest.param("[AB]", ("A", "B"), id="regex"),
)) ])
def test_find_observatories(match, result, requests_mock): def test_find_observatories(match, result, requests_mock):
"""Test `find_observatories()`."""
requests_mock.get( requests_mock.get(
_url(api.find_observatories_path()), _url(api.find_observatories_path()),
json=list(TEST_DATA), json=list(TEST_DATA),
...@@ -106,12 +154,28 @@ def test_find_observatories(match, result, requests_mock): ...@@ -106,12 +154,28 @@ def test_find_observatories(match, result, requests_mock):
assert ui.find_observatories(match=match) == list(set(result)) assert ui.find_observatories(match=match) == list(set(result))
@pytest.mark.parametrize(("site", "match", "result"), ( @pytest.mark.parametrize(("site", "match", "result"), [
(None, None, [ft for site in TEST_DATA for ft in TEST_DATA[site]]), pytest.param(
("A", None, list(TEST_DATA["A"])), None,
("A", "PROD", ["A1_PROD"]), None,
)) [ft for site in TEST_DATA for ft in TEST_DATA[site]],
id="all",
),
pytest.param(
"A",
None,
list(TEST_DATA["A"]),
id="site",
),
pytest.param(
"A",
"PROD",
["A1_PROD"],
id="site-match",
),
])
def test_find_types(site, match, result, requests_mock): def test_find_types(site, match, result, requests_mock):
"""Test `find_types()`."""
if site: if site:
respdata = list(TEST_DATA[site]) respdata = list(TEST_DATA[site])
else: else:
...@@ -127,6 +191,7 @@ def test_find_types(site, match, result, requests_mock): ...@@ -127,6 +191,7 @@ def test_find_types(site, match, result, requests_mock):
def test_find_times(requests_mock): def test_find_times(requests_mock):
"""Test `find_times()`."""
site = "A" site = "A"
frametype = "A1_TEST" frametype = "A1_TEST"
requests_mock.get( requests_mock.get(
...@@ -141,6 +206,7 @@ def test_find_times(requests_mock): ...@@ -141,6 +206,7 @@ def test_find_times(requests_mock):
def test_find_url(requests_mock): def test_find_url(requests_mock):
"""Test `find_url()`."""
urls = [ urls = [
"file:///data/A/A1_TEST/A-A1_TEST-0-1.gwf", "file:///data/A/A1_TEST/A-A1_TEST-0-1.gwf",
"gsiftp://localhost:2811/data/A/A1_TEST/A-A1_TEST-0-1.gwf", "gsiftp://localhost:2811/data/A/A1_TEST/A-A1_TEST-0-1.gwf",
...@@ -157,27 +223,45 @@ def test_find_url(requests_mock): ...@@ -157,27 +223,45 @@ def test_find_url(requests_mock):
) == urls[1:] ) == urls[1:]
def test_find_url_on_missing(requests_mock): @pytest.mark.parametrize(("on_missing", "ctx"), [
# no warnings, no errors
pytest.param("ignore", no_warning(), id="ignore"),
# a warning about validation
pytest.param(
"warn",
pytest.warns(
UserWarning,
match="no files found",
),
id="warn",
),
# an exception about validation
pytest.param(
"raise",
pytest.raises(
RuntimeError,
match="no files found",
),
id="raise",
),
])
def test_find_url_on_missing(requests_mock, on_missing, ctx):
"""Test `find_url` handling of missing data."""
# mock the request
requests_mock.get( requests_mock.get(
_url(api.find_url_path("A-A1_TEST-0-1.gwf")), _url(api.find_url_path("A-A1_TEST-0-1.gwf")),
json=[], json=[],
) )
# on_missing="ignore" with ctx:
with warnings.catch_warnings(): assert ui.find_url(
warnings.simplefilter("error") "A-A1_TEST-0-1.gwf",
assert ui.find_url("A-A1_TEST-0-1.gwf", on_missing="ignore") == [] on_missing=on_missing,
) == []
# on_missing="warn"
with pytest.warns(UserWarning):
assert ui.find_url("A-A1_TEST-0-1.gwf", on_missing="warn") == []
# on_missing="error"
with pytest.raises(RuntimeError):
ui.find_url("A-A1_TEST-0-1.gwf", on_missing="error")
def test_find_latest(requests_mock): def test_find_latest(requests_mock):
"""Test `find_latest()`."""
# NOTE: the target function is essentially identical to # NOTE: the target function is essentially identical to
# find_url, so we just do a minimal smoke test here # find_url, so we just do a minimal smoke test here
urls = [ urls = [
...@@ -192,10 +276,12 @@ def test_find_latest(requests_mock): ...@@ -192,10 +276,12 @@ def test_find_latest(requests_mock):
def _file_url(seg): def _file_url(seg):
return f"file:///data/A/A1_TEST/A-A1_TEST-{seg[0]}-{seg[1]-seg[0]}.gwf" seg = segments.segment(floor(seg[0]), ceil(seg[1]))
return f"file:///data/A/A1_TEST/A-A1_TEST-{seg[0]}-{abs(seg)}.gwf"
def test_find_urls(requests_mock): def test_find_urls(requests_mock):
"""Test `find_urls()`."""
urls = list(map(_file_url, TEST_DATA["A"]["A1_TEST"][:2])) urls = list(map(_file_url, TEST_DATA["A"]["A1_TEST"][:2]))
requests_mock.get( requests_mock.get(
_url(api.find_urls_path("A", "A1_TEST", 0, 20, "file")), _url(api.find_urls_path("A", "A1_TEST", 0, 20, "file")),
...@@ -204,22 +290,43 @@ def test_find_urls(requests_mock): ...@@ -204,22 +290,43 @@ def test_find_urls(requests_mock):
assert ui.find_urls("A", "A1_TEST", 0, 20, on_gaps="error") == urls assert ui.find_urls("A", "A1_TEST", 0, 20, on_gaps="error") == urls
def test_find_urls_on_gaps(requests_mock): @pytest.mark.parametrize(("on_gaps", "ctx"), [
# no warnings, no errors
pytest.param("ignore", no_warning(), id="ignore"),
# a warning about validation
pytest.param(
"warn",
pytest.warns(
UserWarning,
match="^Missing segments",
),
id="warn",
),
# an exception about validation
pytest.param(
"raise",
pytest.raises(
RuntimeError,
match=r"^Missing segments",
),
id="raise",
),
])
def test_find_urls_on_gaps(requests_mock, on_gaps, ctx):
"""Test `find_urls` handling of gaps with ``on_gaps``."""
# configure the mock request
urls = list(map(_file_url, TEST_DATA["A"]["A1_TEST"])) urls = list(map(_file_url, TEST_DATA["A"]["A1_TEST"]))
requests_mock.get( requests_mock.get(
_url(api.find_urls_path("A", "A1_TEST", 0, 100, "file")), _url(api.find_urls_path("A", "A1_TEST", 0, 100, "file")),
json=urls, json=urls,
) )
# on_gaps="ignore" # make the request
with warnings.catch_warnings(): with ctx:
warnings.simplefilter("error") assert ui.find_urls(
assert ui.find_urls("A", "A1_TEST", 0, 100, on_gaps="ignore") == urls "A",
"A1_TEST",
# on_missing="warn" 0,
with pytest.warns(UserWarning): 100,
assert ui.find_urls("A", "A1_TEST", 0, 100, on_gaps="warn") == urls on_gaps=on_gaps,
) == urls
# on_missing="error"
with pytest.raises(RuntimeError):
ui.find_urls("A", "A1_TEST", 0, 100, on_gaps="error")
...@@ -16,14 +16,13 @@ ...@@ -16,14 +16,13 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Tests for :mod:`gwdatafind.utils`. """Tests for :mod:`gwdatafind.utils`."""
"""
from unittest import mock from unittest import mock
import pytest import pytest
from .. import utils from gwdatafind import utils
@mock.patch.dict( @mock.patch.dict(
...@@ -34,6 +33,7 @@ from .. import utils ...@@ -34,6 +33,7 @@ from .. import utils
}, },
) )
def test_get_default_host(): def test_get_default_host():
"""Test `get_default_host()` with both environment variables."""
assert utils.get_default_host() == "gwtest" assert utils.get_default_host() == "gwtest"
...@@ -43,12 +43,17 @@ def test_get_default_host(): ...@@ -43,12 +43,17 @@ def test_get_default_host():
clear=True, clear=True,
) )
def test_get_default_host_ligo(): def test_get_default_host_ligo():
"""Test `get_default_host()` with ``LIGO_DATAFIND_SERVER`` env only."""
assert utils.get_default_host() == "ligotest" assert utils.get_default_host() == "ligotest"
@mock.patch.dict("os.environ", clear=True) @mock.patch.dict("os.environ", clear=True)
def test_get_default_host_error(): def test_get_default_host_error():
with pytest.raises(ValueError): """Test `get_default_host()` error handling."""
with pytest.raises(
ValueError,
match="Failed to determine default gwdatafind host",
):
utils.get_default_host() utils.get_default_host()
...@@ -56,7 +61,8 @@ def test_get_default_host_error(): ...@@ -56,7 +61,8 @@ def test_get_default_host_error():
"igwn_auth_utils.x509.validate_certificate", "igwn_auth_utils.x509.validate_certificate",
side_effect=(None, RuntimeError), side_effect=(None, RuntimeError),
) )
def test_validate_proxy(_): def test_validate_proxy(mock_validate):
"""Test `validate_proxy()`."""
# check that no error ends up as 'True' # check that no error ends up as 'True'
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
assert utils.validate_proxy("something") is True assert utils.validate_proxy("something") is True
...@@ -69,7 +75,8 @@ def test_validate_proxy(_): ...@@ -69,7 +75,8 @@ def test_validate_proxy(_):
"igwn_auth_utils.x509.find_credentials", "igwn_auth_utils.x509.find_credentials",
side_effect=("cert", ("cert", "key")), side_effect=("cert", ("cert", "key")),
) )
def test_find_credential(_): def test_find_credential(mock_find_credentials):
"""Test `find_credential()`."""
# check that if upstream returns a single cert, we still get a tuple # check that if upstream returns a single cert, we still get a tuple
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
assert utils.find_credential() == ("cert", "cert") assert utils.find_credential() == ("cert", "cert")
......
...@@ -37,7 +37,7 @@ from .utils import ( ...@@ -37,7 +37,7 @@ from .utils import (
get_default_host, get_default_host,
) )
__author__ = 'Duncan Macleod <duncan.macleod@ligo.org>' __author__ = "Duncan Macleod <duncan.macleod@ligo.org>"
__all__ = [ __all__ = [
"ping", "ping",
...@@ -80,7 +80,7 @@ def get_json(*args, **kwargs): ...@@ -80,7 +80,7 @@ def get_json(*args, **kwargs):
data : `object` data : `object`
the URL reponse parsed with :func:`json.loads` the URL reponse parsed with :func:`json.loads`
See also See Also
-------- --------
igwn_auth_utils.requests.get igwn_auth_utils.requests.get
for information on how the request is performed for information on how the request is performed
......
...@@ -16,8 +16,7 @@ ...@@ -16,8 +16,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Utilities for the GW datafind service. """Utilities for the GW datafind service."""
"""
import os import os
import warnings import warnings
...@@ -153,9 +152,9 @@ def filename_metadata(filename): ...@@ -153,9 +152,9 @@ def filename_metadata(filename):
file naming convention that includes documenting the GPS start integer file naming convention that includes documenting the GPS start integer
and integer duration of a file, see that document for more details. and integer duration of a file, see that document for more details.
""" """
obs, desc, start, end = os.path.basename(filename).split('-') obs, desc, start, end = os.path.basename(filename).split("-")
start = int(start) start = int(start)
end = int(end.split('.')[0]) end = int(end.split(".")[0])
return obs, desc, segment(start, start+end) return obs, desc, segment(start, start+end)
......
...@@ -52,7 +52,7 @@ docs = [ ...@@ -52,7 +52,7 @@ docs = [
"sphinx-copybutton", "sphinx-copybutton",
] ]
test = [ test = [
"pytest >= 2.8.0", "pytest >= 3.9.3",
"pytest-cov", "pytest-cov",
"requests-mock", "requests-mock",
] ]
...@@ -80,12 +80,26 @@ source = [ ...@@ -80,12 +80,26 @@ source = [
] ]
[tool.coverage.report] [tool.coverage.report]
exclude_lines = [
# ignore when asked
"pragma: no( |-)cover",
# don't complain about typing blocks
"if (typing\\.)?TYPE_CHECKING",
]
precision = 1 precision = 1
[tool.coverage.run] [tool.coverage.run]
source = ["gwdatafind"] source = ["gwdatafind"]
[tool.mypy]
check_untyped_defs = true
exclude = [
"^docs/",
]
ignore_missing_imports = true
[tool.pytest.ini_options] [tool.pytest.ini_options]
minversion = "3.9.3"
addopts = "-r a --color=yes" addopts = "-r a --color=yes"
filterwarnings = [ filterwarnings = [
"error", "error",
...@@ -93,24 +107,17 @@ filterwarnings = [ ...@@ -93,24 +107,17 @@ filterwarnings = [
] ]
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = ["ALL"]
# mccabe ignore = [
"C90", "ANN003", # type annotations for **kwargs
# pycodestyle errors "D203", # blank line before class docstring
"E", "D213", # docstring summary on second line
# flake8-executable "D413", # blank line after last section
"EXE", "PLR0913", # too many arguments
# pyflakes "SIM108", # if-else instead of ternary if
"F",
# isort
"I",
# pep8-naming
"N",
# pyupgrade
"UP",
# pycodestyle warnings
"W",
] ]
# allow 'mock_...' variables to go unused in tests
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?)|mock_.*)$"
[tool.ruff.lint.isort] [tool.ruff.lint.isort]
combine-as-imports = true combine-as-imports = true
...@@ -121,6 +128,19 @@ force-wrap-aliases = true ...@@ -121,6 +128,19 @@ force-wrap-aliases = true
"F401", # unused-import "F401", # unused-import
"F403", # undefined-local-with-import-star "F403", # undefined-local-with-import-star
] ]
"*/tests/*" = [
"ANN", # type annotations
"B904", # raise from
"EM101", # string literal in exception
"PLR2004", # magic value used in comparison
"S101", # assert
]
"docs/*" = [
"A", # builtins
"ANN", # type annotations
"D", # docstrings
"INP001", # implicit namespace package
]
[tool.setuptools] [tool.setuptools]
license-files = [ "LICENSE" ] license-files = [ "LICENSE" ]
......
...@@ -28,7 +28,7 @@ BuildRequires: python3dist(igwn-segments) ...@@ -28,7 +28,7 @@ BuildRequires: python3dist(igwn-segments)
# testing dependencies # testing dependencies
BuildRequires: man-db BuildRequires: man-db
BuildRequires: python3dist(pytest) >= 2.8.0 BuildRequires: python3dist(pytest) >= 3.1.0
BuildRequires: python3dist(requests-mock) BuildRequires: python3dist(requests-mock)
# -- src.rpm # -- src.rpm
...@@ -100,6 +100,12 @@ console_scripts = ...@@ -100,6 +100,12 @@ console_scripts =
[build_manpages] [build_manpages]
manpages = manpages =
man/gw_data_find.1:prog=gwdatafind:function=command_line:module=gwdatafind.__main__ man/gw_data_find.1:prog=gwdatafind:function=command_line:module=gwdatafind.__main__
[tool:pytest]
minversion = 3.1.0
addopts = -r a
filterwarnings =
error
ignore:.*pkg_resources
SETUP_CFG SETUP_CFG
%endif %endif
%if %{undefined pyproject_wheel} %if %{undefined pyproject_wheel}
...@@ -136,7 +142,12 @@ export PYTHONPATH="%{buildroot}%{python3_sitelib}" ...@@ -136,7 +142,12 @@ export PYTHONPATH="%{buildroot}%{python3_sitelib}"
%{__python3} -m gwdatafind --help %{__python3} -m gwdatafind --help
%{buildroot}%{_bindir}/gw_data_find --help %{buildroot}%{_bindir}/gw_data_find --help
# run test suite # run test suite
%if 0%{?rhel} == 0 || 0%{?rhel} >= 9
%{pytest} --pyargs gwdatafind %{pytest} --pyargs gwdatafind
%else
# pytest < 3.9 (EPEL8) can't handle 'tmp_path' fixture
%{pytest} --pyargs gwdatafind -k "not test_main["
%endif
# test man pages # test man pages
env MANPATH="%{buildroot}%{_mandir}" man -P cat gw_data_find env MANPATH="%{buildroot}%{_mandir}" man -P cat gw_data_find
......