1
0
forked from Mirrors/itch-dl
Files
itchdl/itch_dl/config.py
Ryszard Knop d307ae8db7 Remove Pydantic and update remaining dependencies
Pydantic was used only for validating settings. Since we don't have any
really complex types there, we can easily do it manually. We would have
to upgrade to Pydantic V2 soon, and that means +6.5MB of dependencies,
so let's drop it and enjoy a smaller install venv.
2025-02-14 15:41:14 +01:00

107 lines
3.3 KiB
Python

import os
import json
import logging
import platform
import argparse
from dataclasses import dataclass, fields
from typing import Optional, Any, get_type_hints
import requests
from . import __version__
@dataclass
class Settings:
"""Available settings for itch-dl. Make sure all of them
have default values, as the config file may not exist."""
api_key: Optional[str] = None
user_agent: str = f"python-requests/{requests.__version__} itch-dl/{__version__}"
download_to: Optional[str] = None
mirror_web: bool = False
urls_only: bool = False
parallel: int = 1
filter_files_glob: Optional[str] = None
filter_files_regex: Optional[str] = None
verbose: bool = False
def create_and_get_config_path() -> str:
"""Returns the configuration directory in the appropriate
location for the current OS. The directory may not exist."""
system = platform.system()
if system == "Linux":
base_path = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config/")
elif system == "Darwin":
base_path = os.path.expanduser("~/Library/Application Support/")
elif system == "Windows":
base_path = os.environ.get("APPDATA") or os.path.expanduser("~/AppData/Roaming/")
else:
raise NotImplementedError(f"Unknown platform: {system}")
return os.path.join(base_path, "itch-dl")
def clean_config(config_data: dict[str, Any]) -> dict[str, Any]:
cleaned_config = {}
settings_invalid = False
type_hints = get_type_hints(Settings)
# Complain about invalid types, if any:
for key, value in config_data.items():
if not (expected_type := type_hints.get(key)):
logging.warning("Settings contain an unknown item, ignoring: '%s'", key)
continue
if not isinstance(value, expected_type):
logging.fatal("Settings.%s has invalid type '%s', expected '%s'", key, type(value), expected_type)
# Keep iterating to look up all the bad keys:
settings_invalid = True
continue
cleaned_config[key] = value
if settings_invalid:
logging.fatal("Settings invalid, bailing out!")
exit(1)
return cleaned_config
def load_config(args: argparse.Namespace, profile: Optional[str] = None) -> Settings:
"""Loads the configuration from the file system if it exists,
the returns a Settings object."""
config_path = create_and_get_config_path()
config_file_path = os.path.join(config_path, "config.json")
profile_file_path = os.path.join(config_path, "profiles", profile or "")
if os.path.isfile(config_file_path):
logging.debug("Found config file: %s", config_file_path)
with open(config_file_path) as f:
config_data = json.load(f)
else:
config_data = {}
if os.path.isfile(profile_file_path):
logging.debug("Found profile: %s", profile_file_path)
with open(config_file_path) as f:
profile_data = json.load(f)
config_data.update(profile_data)
# All settings from the base file:
settings = Settings(**clean_config(config_data))
# Apply overrides from CLI args on each field in Settings:
for field in fields(Settings):
key = field.name
if value := getattr(args, key):
setattr(settings, key, value)
return settings