Source code for bandersnatch.configuration
"""
Module containing classes to access the bandersnatch configuration file
"""
import configparser
import logging
import warnings
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Type
try:
import importlib.resources
except ImportError: # pragma: no cover
# For <=3.6
import importlib
import importlib_resources
importlib.resources = importlib_resources
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
[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( # type: ignore
"bandersnatch", "default.conf"
) as config_path:
self.default_config_file = str(config_path)
self.config_file = config_file
self.load_configuration()
self.check_for_deprecations()
[docs] def check_for_deprecations(self) -> None:
if self.SHOWN_DEPRECATIONS:
return
if self.config.has_section("whitelist") or self.config.has_section("blacklist"):
err_msg = (
"whitelist/blacklist filter plugins will be renamed to "
"allowlist_*/blocklist_* in version 5.0 "
" - Documentation @ https://bandersnatch.readthedocs.io/"
)
warnings.warn(err_msg, DeprecationWarning, stacklevel=2)
logger.warning(err_msg)
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(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
return SetConfigValues(
json_save,
root_uri,
diff_file_path,
diff_append_epoch,
digest_name,
storage_backend_name,
cleanup,
)