# SPDX-FileCopyrightText: 2023-present The Firebird Projects <www.firebirdsql.org>
#
# SPDX-License-Identifier: MIT
#
# PROGRAM/MODULE: saturnin
# FILE: saturnin/base/config.py
# DESCRIPTION: Saturnin configuration
# CREATED: 6.2.2023
#
# The contents of this file are subject to the MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Copyright (c) 2023 Firebird Project (www.firebirdsql.org)
# All Rights Reserved.
#
# Contributor(s): Pavel Císař (original code)
# ______________________________________
"""Code for configuration of Saturnin installation.
"""
from __future__ import annotations
from typing import Optional, List, Final
import sys
import os
import sysconfig
from pathlib import Path
from configparser import ConfigParser, ExtendedInterpolation
from firebird.base.config import DirectoryScheme, get_directory_scheme, Config, StrOption
#: filename for Saturnin configuration file
SATURNIN_CFG: Final[str] = 'saturnin.conf'
#: filename for Firebird configuration file
FIREBIRD_CFG: Final[str] = 'firebird.conf'
#: filename for logging configuration file
LOGGING_CFG: Final[str] = 'logging.conf'
#: Saturnin configuration file header
CONFIG_HDR: Final[str] = """;
; A configuration file consists of sections, each led by a [section] header, followed by
; key/value entries separated by = or : string. Section names are case sensitive but keys
; are not. Leading and trailing whitespace is removed from keys and values. Values can be
; omitted, in which case the key/value delimiter may also be left out. Values can also
; span multiple lines, as long as they are indented deeper than the first line of the value.
;
; Configuration files may include comments, prefixed by # and ; characters. Comments may
; appear on their own on an otherwise empty line, possibly indented.
;
; Values can contain ${section:option} format strings which refer to other values.
; If the section: part is omitted, interpolation defaults to the current section (and possibly
; the default values from the special DEFAULT section). Interpolation can span multiple levels.
"""
#: True if current platform is Windows
WINDOWS: Final[bool] = sys.platform == "win32"
#: True if current platform is based on MINGW
MINGW: Final[bool] = sysconfig.get_platform().startswith("mingw")
[docs]
class SaturninScheme(DirectoryScheme):
"""Saturnin platform directory scheme.
When SATURNIN_HOME environment variable is not set, and Saturnin resides in virtual
environment that contains `home` subdirectory, it's set as Saturnin HOME directory.
This `home` subdirectory is created by `saturnin create home` command on request.
"""
def __init__(self):
super().__init__('saturnin')
if not self.has_home_env() and is_virtual():
home_dir: Path = venv() / 'home'
if home_dir.is_dir():
os.environ['SATURNIN_HOME'] = str(home_dir)
self.dir_map.update(get_directory_scheme('saturnin').dir_map)
self.__pip_path: Path = 'pip'
self.__pip_cmd: List[str] = ['pip']
if is_virtual():
root = venv()
if WINDOWS:
bin_path = root / "Scripts" if not MINGW else root / "bin"
python_path = bin_path / "python.exe"
else:
bin_path = root / "bin"
python_path = bin_path / "python"
pip_path = bin_path / 'pip'
if pip_path.is_file():
self.__pip_path = pip_path
self.__pip_cmd = [str(pip_path)]
else:
# No pip shortcut in venv, we must relly on python -m to run it, typical for pipx
self.__pip_path = None
self.__pip_cmd = [str(python_path), '-m', 'pip']
[docs]
def get_pip_cmd(self, *args) -> List[str]:
"""Returns list with command to run pip.
Arguments:
args: Arguments for pip
"""
result = self.__pip_cmd.copy()
result.extend(args)
return result
@property
def recipes(self) -> Path:
"""Path to directory with recipe files.
"""
return self.data / 'recipes'
@property
def pids(self) -> Path:
"""Path to directory with PID files for running daemons.
"""
return self.run_data / 'pids'
@property
def site_services_toml(self) -> Path:
"""Saturnin service registry file.
"""
return self.data / 'services.toml'
@property
def site_apps_toml(self) -> Path:
"""Saturnin application registry file.
"""
return self.data / 'apps.toml'
@property
def site_oids_toml(self) -> Path:
"""Saturnin OID registry file.
"""
return self.data / 'oids.toml'
@property
def site_conf(self) -> Path:
"""Saturnin site configuration file.
"""
return self.config / SATURNIN_CFG
@property
def user_conf(self) -> Path:
"""Saturnin user configuration file.
"""
return self.user_config / SATURNIN_CFG
@property
def firebird_conf(self) -> Path:
"""Firebird driver configuration file.
"""
return self.config / FIREBIRD_CFG
@property
def logging_conf(self) -> Path:
"""Python logging configuration file.
"""
return self.config / LOGGING_CFG
@property
def log_file(self) -> Path:
"""Saturnin log file.
"""
return self.logs / 'saturnin.log'
@property
def history_file(self) -> Path:
"""Saturnin console command history file.
"""
return self.data / 'saturnin.hist'
@property
def theme_file(self) -> Path:
"""Saturnin console theme file.
"""
return self.config / 'theme.conf'
@property
def pip_path(self) -> Path:
"""Path to `pip`.
"""
return self.__pip_path
[docs]
class SaturninConfig(Config):
"""Saturnin (a Firebird Butler platform) configuration.
"""
def __init__(self):
super().__init__('saturnin')
#: External editor
self.editor: StrOption = StrOption('editor', "External editor",
default=os.getenv('EDITOR'))
[docs]
def is_virtual() -> bool:
"""Returns True if Saturnin runs in a virtual environtment.
"""
# Check supports venv && virtualenv >= 20.0.0
return getattr(sys, 'base_prefix', sys.prefix) != sys.prefix
[docs]
def venv() -> Optional[Path]:
"""Path to Saturnin virtual environment.
"""
return Path(sys.prefix) if is_virtual() else None
# Set SATURNIN_HOME if defined in virtual environment
if is_virtual():
path: Path = venv() / 'home'
if path.is_dir():
os.environ['SATURNIN_HOME'] = str(path)
#: Active Saturnin directory scheme
directory_scheme: SaturninScheme = SaturninScheme()
#: Saturnin configuration object
saturnin_config: SaturninConfig = SaturninConfig()
parser: ConfigParser = ConfigParser(interpolation=ExtendedInterpolation())
parser.read([directory_scheme.site_conf, directory_scheme.user_conf])
if parser.has_section('saturnin'):
saturnin_config.load_config(parser)
del parser