skyline-apiserver/libs/skyline-config/skyline_config/config.py
Gao Hanxiang c81b6e11d2 test: Add skyline-config unit test
1. Add skyline-config unit test case and tools
2. Update immutables package type annotation
3. Adjust get_config_path function

Change-Id: Idc70ac164247c0d9cd5135f4e5bbef3994d05f22
2021-09-29 04:30:26 +00:00

168 lines
5.5 KiB
Python

# Copyright 2021 99cloud
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import warnings
from dataclasses import InitVar, dataclass, field
from pathlib import Path, PurePath
from typing import Any, Dict, Iterator, NamedTuple, Sequence, Tuple, Type
import yaml
from immutables import Map, MapItems, MapKeys, MapValues
from pydantic import BaseModel, create_model
class ConfigPath(NamedTuple):
config_dir_path: str
config_file_path: str
@dataclass(frozen=True)
class Opt:
name: str
description: str
schema: Any
default: Any = None
deprecated: bool = False
value: Any = field(init=False, default=None)
_schema_model: Type[BaseModel] = field(init=False, repr=False)
def __post_init__(self) -> None:
object.__setattr__(
self,
"_schema_model",
create_model(f"Opt(name='{self.name}')", value=(self.schema, ...)),
)
def load(self, value: Any) -> None:
value = self.default if value is None else value
self._schema_model(value=value)
object.__setattr__(self, "value", value)
if self.deprecated:
warnings.warn(
f"The config opt {self.name} is deprecated, will be deleted in the"
" future version",
DeprecationWarning,
)
@dataclass(repr=False, frozen=True)
class Group:
name: str
init_opts: InitVar[Sequence[Opt]] = tuple()
_opts: Map[str, Opt] = field(init=False, repr=False)
def __post_init__(self, init_opts: Sequence[Opt]) -> None:
object.__setattr__(self, "_opts", Map({opt.name: opt for opt in init_opts}))
def __getattr__(self, name: str) -> Any:
if name in self._opts:
return self._opts[name].value
raise AttributeError(name)
def __contains__(self, key: Any) -> bool:
return self._opts.__contains__(key)
def __iter__(self) -> Iterator[Any]:
return self._opts.__iter__()
def __len__(self) -> int:
return self._opts.__len__()
def __repr__(self) -> str:
items = ", ".join((f"{opt}=Opt(name='{opt}')" for opt in self._opts))
return f"Group({items})"
def keys(self) -> MapKeys[str]:
return self._opts.keys()
def values(self) -> MapValues[Opt]:
return self._opts.values()
def items(self) -> MapItems[str, Opt]:
return self._opts.items()
@dataclass(repr=False, frozen=True)
class Configuration:
init_groups: InitVar[Sequence[Group]] = tuple()
config: Dict[str, Any] = field(init=False, default_factory=dict, repr=False)
_groups: Map[str, Group] = field(init=False, repr=False)
def __post_init__(self, init_groups: Sequence[Group]) -> None:
object.__setattr__(self, "_groups", Map({group.name: group for group in init_groups}))
@staticmethod
def get_config_path(project: str, env: Dict[str, str]) -> Tuple[str, str]:
config_dir_path = env.get("OS_CONFIG_DIR", PurePath("/etc", project).as_posix())
config_file_path = PurePath(config_dir_path).joinpath(f"{project}.yaml").as_posix()
return ConfigPath(config_dir_path.strip(), config_file_path.strip())
def setup(self, project: str, env: Dict[str, str]) -> None:
config_dir_path, config_file_path = self.get_config_path(project, env)
if not Path(config_file_path).exists():
raise ValueError(f"Not found config file: {config_file_path}")
with open(config_file_path) as f:
try:
object.__setattr__(self, "config", yaml.safe_load(f))
except Exception:
raise ValueError("Load config file error")
for group in self._groups.values():
for opt in group._opts.values():
value = self.config.get(group.name, {}).get(opt.name)
opt.load(value)
def cleanup(self) -> None:
for group in self._groups.values():
for opt in group._opts.values():
object.__setattr__(opt, "value", None)
object.__setattr__(self, "_groups", Map())
object.__setattr__(self, "config", {})
def __call__(self, init_groups: Sequence[Group]) -> Any:
object.__setattr__(self, "_groups", Map({group.name: group for group in init_groups}))
def __getattr__(self, name: str) -> Group:
if name in self._groups:
return self._groups[name]
raise AttributeError(name)
def __contains__(self, key: Any) -> bool:
return self._groups.__contains__(key)
def __iter__(self) -> Iterator[Any]:
return self._groups.__iter__()
def __len__(self) -> int:
return self._groups.__len__()
def __repr__(self) -> str:
items = ", ".join((f"{group}=Group(name='{group}')" for group in self._groups))
return f"Configuration({items})"
def keys(self) -> MapKeys[str]:
return self._groups.keys()
def values(self) -> MapValues[Group]:
return self._groups.values()
def items(self) -> MapItems[str, Group]:
return self._groups.items()
__all__ = ("Opt", "Group", "Configuration")