Skip to content

Commit 63669b9

Browse files
authored
Improve invalid backend error (#626)
* Enhance backend error message * Add config file info to backend errors (#627) * Improve backend error message * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add config test to make sure full coverage * Address review comment - missed saving resolve!
1 parent 2dee3c5 commit 63669b9

File tree

4 files changed

+59
-8
lines changed

4 files changed

+59
-8
lines changed

servicex/configuration.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class Configuration(BaseModel):
4949
)
5050

5151
shortened_downloaded_filename: Optional[bool] = False
52+
# Path to the configuration file this object was read from. This field is
53+
# populated by :py:meth:`Configuration.read` and is not part of the input
54+
# schema.
55+
config_file: Optional[str] = Field(default=None, exclude=True)
5256

5357
@model_validator(mode="after")
5458
def expand_cache_path(self):
@@ -96,12 +100,17 @@ def read(cls, config_path: Optional[str] = None):
96100
:return: Populated configuration object
97101
"""
98102
if config_path:
99-
yaml_config = cls._add_from_path(Path(config_path), walk_up_tree=False)
103+
yaml_config, cfg_path = cls._add_from_path(
104+
Path(config_path), walk_up_tree=False
105+
)
100106
else:
101-
yaml_config = cls._add_from_path(walk_up_tree=True)
107+
yaml_config, cfg_path = cls._add_from_path(walk_up_tree=True)
102108

103109
if yaml_config:
104-
return Configuration.model_validate(yaml_config)
110+
cfg = Configuration.model_validate(yaml_config)
111+
if cfg_path:
112+
cfg.config_file = str(cfg_path)
113+
return cfg
105114
else:
106115
path_extra = f"in {config_path}" if config_path else ""
107116
raise NameError(
@@ -111,8 +120,9 @@ def read(cls, config_path: Optional[str] = None):
111120
@classmethod
112121
def _add_from_path(cls, path: Optional[Path] = None, walk_up_tree: bool = False):
113122
config = None
123+
found_file: Optional[Path] = None
114124
if path:
115-
path.resolve()
125+
path = path.resolve()
116126
name = path.name
117127
dir = path.parent.resolve()
118128
alt_name = None
@@ -126,14 +136,16 @@ def _add_from_path(cls, path: Optional[Path] = None, walk_up_tree: bool = False)
126136
if f.exists():
127137
with open(f) as config_file:
128138
config = yaml.safe_load(config_file)
129-
break
139+
found_file = f
140+
break
130141

131142
if alt_name:
132143
f = dir / alt_name # if neither option above, find servicex.yaml
133144
if f.exists():
134145
with open(f) as config_file:
135146
config = yaml.safe_load(config_file)
136-
break
147+
found_file = f
148+
break
137149

138150
if not walk_up_tree:
139151
break
@@ -155,6 +167,7 @@ def _add_from_path(cls, path: Optional[Path] = None, walk_up_tree: bool = False)
155167
if f.exists():
156168
with open(f) as config_file:
157169
config = yaml.safe_load(config_file)
170+
found_file = f
158171
break
159172

160-
return config
173+
return config, found_file

servicex/servicex_client.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,12 @@ def __init__(self, backend=None, url=None, config_path=None):
343343
self.servicex = ServiceXAdapter(url)
344344
elif backend:
345345
if backend not in self.endpoints:
346-
raise ValueError(f"Backend {backend} not defined in .servicex file")
346+
valid_backends = ", ".join(self.endpoints.keys())
347+
cfg_file = self.config.config_file or ".servicex"
348+
raise ValueError(
349+
f"Backend {backend} not defined in {cfg_file} file. "
350+
f"Valid backend names: {valid_backends}"
351+
)
347352
self.servicex = ServiceXAdapter(
348353
self.endpoints[backend].endpoint,
349354
refresh_token=self.endpoints[backend].token,

tests/test_config.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,25 @@ def test_read_from_home(monkeypatch, tmp_path):
9292

9393
c = Configuration.read()
9494
assert c.api_endpoints[0].endpoint == "http://localhost:5000"
95+
96+
97+
@pytest.mark.parametrize("config_filename", ["servicex.yaml", ".servicex"])
98+
def test_read_from_default_files(monkeypatch, tmp_path, config_filename):
99+
"""
100+
Ensure config can be located in the user's home directory for servicex.yaml and .servicex.
101+
"""
102+
103+
# Create a fake home directory with the config file
104+
cfg = tmp_path / config_filename
105+
cfg.write_text(
106+
"""
107+
api_endpoints:
108+
- endpoint: http://localhost:5012
109+
name: localhost
110+
"""
111+
)
112+
113+
monkeypatch.chdir(tmp_path)
114+
115+
c = Configuration.read()
116+
assert c.api_endpoints[0].endpoint == "http://localhost:5012"

tests/test_servicex_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2727
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2828
from datetime import datetime
29+
from pathlib import Path
2930
from unittest.mock import MagicMock, patch
3031

3132
import pytest
@@ -138,3 +139,13 @@ def test_delete_transform_from_cache(mock_cache, servicex_adaptor, transformed_r
138139
mock_cache.return_value.delete_record_by_request_id.assert_called_once_with(
139140
"servicex-request-789"
140141
)
142+
143+
144+
def test_invalid_backend_raises_error_with_filename():
145+
config_file = "tests/example_config.yaml"
146+
expected = Path(config_file).resolve()
147+
148+
with pytest.raises(ValueError) as err:
149+
ServiceXClient(backend="badname", config_path=config_file)
150+
151+
assert f"Backend badname not defined in {expected} file" in str(err.value)

0 commit comments

Comments
 (0)