Source code for servicex.configuration

# Copyright (c) 2022, IRIS-HEP
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import tempfile
from pathlib import Path, PurePath
from typing import List, Optional, Dict

from pydantic import BaseModel, Field, AliasChoices, model_validator

import yaml


[docs] class Endpoint(BaseModel): endpoint: str name: str token: Optional[str] = ""
[docs] class Configuration(BaseModel): api_endpoints: List[Endpoint] default_endpoint: Optional[str] = Field(alias="default-endpoint", default=None) cache_path: Optional[str] = Field( validation_alias=AliasChoices("cache-path", "cache_path"), default=None ) shortened_downloaded_filename: Optional[bool] = False
[docs] @model_validator(mode="after") def expand_cache_path(self): """ Expand the cache path to a full path, and create it if it doesn't exist. Expand ${USER} to be the user name on the system. Works for windows, too. :param v: :return: """ # create a folder inside the tmp directory if not specified in cache_path if not self.cache_path: self.cache_path = "/tmp/servicex_${USER}" s_path = os.path.expanduser(self.cache_path) # If they have tried to use the USER or UserName as an expansion, and it has failed, then # translate it to maintain harmony across platforms. if "${USER}" in s_path and "UserName" in os.environ: s_path = s_path.replace("${USER}", os.environ["UserName"]) if "${USER}" in s_path and "USER" in os.environ: s_path = s_path.replace("${USER}", os.environ["USER"]) p_p = PurePath(s_path) if len(p_p.parts) > 1 and p_p.parts[1] == "tmp": p = Path(tempfile.gettempdir()) / Path(*p_p.parts[2:]) else: p = Path(p_p) p.mkdir(exist_ok=True, parents=True) self.cache_path = p.as_posix() return self
model_config = {"populate_by_name": True}
[docs] def endpoint_dict(self) -> Dict[str, Endpoint]: return {endpoint.name: endpoint for endpoint in self.api_endpoints}
[docs] @classmethod def read(cls, config_path: Optional[str] = None): r""" Read configuration from .servicex or servicex.yaml file. :param config_path: If provided, use this as the path to the .servicex file. Otherwise, search, starting from the current working directory and look in enclosing directories :return: Populated configuration object """ if config_path: yaml_config = cls._add_from_path(Path(config_path), walk_up_tree=False) else: yaml_config = cls._add_from_path(walk_up_tree=True) if yaml_config: return Configuration.model_validate(yaml_config) else: path_extra = f"in {config_path}" if config_path else "" raise NameError( "Can't find .servicex or servicex.yaml config file " + path_extra )
@classmethod def _add_from_path(cls, path: Optional[Path] = None, walk_up_tree: bool = False): config = None if path: path.resolve() name = path.name dir = path.parent.resolve() alt_name = None else: name = ".servicex" alt_name = "servicex.yaml" dir = Path(os.getcwd()) while True: f = dir / name # user-defined path or .servicex if f.exists(): with open(f) as config_file: config = yaml.safe_load(config_file) break if alt_name: f = dir / alt_name # if neither option above, find servicex.yaml if f.exists(): with open(f) as config_file: config = yaml.safe_load(config_file) break if not walk_up_tree: break if dir == dir.parent: break dir = dir.parent return config