"""
Module containing classes to access the bandersnatch configuration file
"""
import configparser
import importlib.resources
import logging
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Type
logger = logging.getLogger("bandersnatch")
[docs]class SetConfigValues(NamedTuple):
json_save: bool
root_uri: str
diff_file_path: str
diff_append_epoch: bool
digest_name: str
storage_backend_name: str
cleanup: bool
release_files_save: bool
compare_method: str
[docs]class Singleton(type): # pragma: no cover
_instances: Dict["Singleton", Type] = {}
def __call__(cls, *args: Any, **kwargs: Any) -> Type:
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
[docs]class BandersnatchConfig(metaclass=Singleton):
# Ensure we only show the deprecations once
SHOWN_DEPRECATIONS = False
def __init__(self, config_file: Optional[str] = None) -> None:
"""
Bandersnatch configuration class singleton
This class is a singleton that parses the configuration once at the
start time.
Parameters
==========
config_file: str, optional
Path to the configuration file to use
"""
self.found_deprecations: List[str] = []
with importlib.resources.path("bandersnatch", "default.conf") as config_path:
self.default_config_file = str(config_path)
self.config_file = config_file
self.load_configuration()
# Keeping for future deprecations ... Commenting to save function call etc.
# self.check_for_deprecations()
[docs] def check_for_deprecations(self) -> None:
if self.SHOWN_DEPRECATIONS:
return
self.SHOWN_DEPRECATIONS = True
[docs] def load_configuration(self) -> None:
"""
Read the configuration from a configuration file
"""
config_file = self.default_config_file
if self.config_file:
config_file = self.config_file
self.config = configparser.ConfigParser(delimiters="=")
self.config.optionxform = lambda option: option # type: ignore
self.config.read(config_file)
# 11-15, 84-89, 98-99, 117-118, 124-126, 144-149
[docs]def validate_config_values( # noqa: C901
config: configparser.ConfigParser,
) -> SetConfigValues:
try:
json_save = config.getboolean("mirror", "json")
except configparser.NoOptionError:
logger.error(
"Please update your config to include a json "
+ "boolean in the [mirror] section. Setting to False"
)
json_save = False
try:
root_uri = config.get("mirror", "root_uri")
except configparser.NoOptionError:
root_uri = ""
try:
diff_file_path = config.get("mirror", "diff-file")
except configparser.NoOptionError:
diff_file_path = ""
if "{{" in diff_file_path and "}}" in diff_file_path:
diff_file_path = diff_file_path.replace("{{", "").replace("}}", "")
diff_ref_section, _, diff_ref_key = diff_file_path.partition("_")
try:
diff_file_path = config.get(diff_ref_section, diff_ref_key)
except (configparser.NoOptionError, configparser.NoSectionError):
logger.error(
"Invalid section reference in `diff-file` key. "
"Please correct this error. Saving diff files in"
" base mirror directory."
)
diff_file_path = str(
Path(config.get("mirror", "directory")) / "mirrored-files"
)
try:
diff_append_epoch = config.getboolean("mirror", "diff-append-epoch")
except configparser.NoOptionError:
diff_append_epoch = False
try:
logger.debug("Checking config for storage backend...")
storage_backend_name = config.get("mirror", "storage-backend")
logger.debug("Found storage backend in config!")
except configparser.NoOptionError:
storage_backend_name = "filesystem"
logger.debug(
"Failed to find storage backend in config, falling back to default!"
)
logger.info(f"Selected storage backend: {storage_backend_name}")
try:
digest_name = config.get("mirror", "digest_name")
except configparser.NoOptionError:
digest_name = "sha256"
if digest_name not in ("md5", "sha256"):
raise ValueError(
f"Supplied digest_name {digest_name} is not supported! Please "
+ "update digest_name to one of ('sha256', 'md5') in the [mirror] "
+ "section."
)
try:
cleanup = config.getboolean("mirror", "cleanup")
except configparser.NoOptionError:
logger.debug(
"bandersnatch is not cleaning up non PEP 503 normalized Simple "
+ "API directories"
)
cleanup = False
release_files_save = config.getboolean("mirror", "release-files", fallback=True)
if not release_files_save and not root_uri:
root_uri = "https://files.pythonhosted.org"
logger.error(
"Please update your config to include a root_uri in the [mirror] "
+ "section when disabling release file sync. Setting to "
+ root_uri
)
try:
logger.debug("Checking config for compare method...")
compare_method = config.get("mirror", "compare-method")
logger.debug("Found compare method in config!")
except configparser.NoOptionError:
compare_method = "hash"
logger.debug(
"Failed to find compare method in config, falling back to default!"
)
if compare_method not in ("hash", "stat"):
raise ValueError(
f"Supplied compare_method {compare_method} is not supported! Please "
+ "update compare_method to one of ('hash', 'stat') in the [mirror] "
+ "section."
)
logger.info(f"Selected compare method: {compare_method}")
return SetConfigValues(
json_save,
root_uri,
diff_file_path,
diff_append_epoch,
digest_name,
storage_backend_name,
cleanup,
release_files_save,
compare_method,
)