From 709e6f25fa164615ac7e19144f05d083ea2dc620 Mon Sep 17 00:00:00 2001 From: Mathew Sir Guest the best Date: Sat, 30 May 2026 19:43:38 -0600 Subject: [PATCH] refactor: replace configobj spec with pydantic-settings and Click CLI Drop the config.spec/configobj configuration in favour of an AppSettings pydantic-settings model (settings.py) loaded from SMILEYFACE_-prefixed env vars or a .env file. Introduce AppContext (context.py) to carry settings plus logging, dispatch commands through a Click CLI (cli.py), and centralise log setup (logging_setup.py). Update hub_machine, datalayer, and scraping modules to consume the new context. Add .env.example and ignore .env. Co-Authored-By: Claude Opus 4.8 (1M context) --- .env.example | 15 +++ .gitignore | 1 + smileyface/__init__.py | 38 ------- smileyface/app.py | 129 +----------------------- smileyface/cli.py | 145 +++++++++++++++++++++++++++ smileyface/config.spec | 62 ------------ smileyface/context.py | 16 +++ smileyface/datalayer/datalayer.py | 6 +- smileyface/datalayer/db_ops.py | 8 +- smileyface/hub_machine.py | 56 +++++------ smileyface/logging_setup.py | 41 ++++++++ smileyface/scrape_latest/local_fs.py | 2 +- smileyface/settings.py | 22 ++++ 13 files changed, 277 insertions(+), 264 deletions(-) create mode 100644 .env.example create mode 100644 smileyface/cli.py delete mode 100644 smileyface/config.spec create mode 100644 smileyface/context.py create mode 100644 smileyface/logging_setup.py create mode 100644 smileyface/settings.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c472549 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# SmileyFace UT4 Server Configuration +# Copy to .env and fill in your values + +SMILEYFACE_PROJECT_DIR= +SMILEYFACE_CONFIG_DIR= +SMILEYFACE_DOWNLOAD_URL= +SMILEYFACE_DOWNLOAD_FILENAME= +SMILEYFACE_DOWNLOAD_MD5= +SMILEYFACE_SKIP_VALIDATE=false +SMILEYFACE_REDIRECT_PROTOCOL= +SMILEYFACE_REDIRECT_URL= +SMILEYFACE_REMOTE_GAME_HOST= +SMILEYFACE_REMOTE_GAME_DIR= +SMILEYFACE_REMOTE_REDIRECT_HOST= +SMILEYFACE_SQLITE_FILENAME=smiles.db diff --git a/.gitignore b/.gitignore index b8acba0..6716010 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist/ __pycache__ *.egg-info idea +.env diff --git a/smileyface/__init__.py b/smileyface/__init__.py index 3f0dd3a..dd5bfa2 100644 --- a/smileyface/__init__.py +++ b/smileyface/__init__.py @@ -1,42 +1,4 @@ -import logging -import sys - -# Module parameters and constants APP_NAME = "SmileyFace Unreal Tournament 4 Server Panel" -APP_AUTHOR = "Mathew Guest" APP_VERSION = "0.1.0" -APP_CONFIG_FILENAME = "config.ini" - -# config.spec is relative to the module src directory and is the -# config specification (structure, names, and types of config file) -APP_CONFIGSPEC_FILENAME = "config.spec" - -# Check and gracefully fail if the user needs to install a 3rd-party dep. -required_lib_names = ["appdirs", "configobj", "colorlog"] - - -def check_env_has_dependencies(required_lib_names): - """ - Attempts to import each module and gracefully fails if it doesn't - exist. - """ - rc = True - for libname in required_lib_names: - try: - __import__(libname) - except ImportError as ex: - print("missing third-part library: ", ex, file=sys.stderr) - rc = False - except Exception as ex: - print(ex, type(ex)) - rc = False - return rc - - -if not check_env_has_dependencies(required_lib_names): - print("refusing to load program without installed dependencies", file=sys.stderr) - raise ImportError("python environment needs third-party dependencies installed") - -# Exposed from sub-modules: from .app import start_app diff --git a/smileyface/app.py b/smileyface/app.py index f7cbb34..0950ebe 100644 --- a/smileyface/app.py +++ b/smileyface/app.py @@ -1,130 +1,5 @@ -import app_skellington -from app_skellington import _util - -from . import ( - datalayer, - hub_machine, - scrape_latest, -) - - -class SmileyFace(app_skellington.ApplicationContainer): - def __init__(self, *args, **kwargs): - filename = "config.spec" - self.configspec_filepath = _util.get_asset(__name__, filename) - - config_filepath = self._get_config_filepath("smileyface-ut4", "", "hub-config.ini") - - super().__init__( - configspec_filepath=self.configspec_filepath, - configini_filepath=config_filepath, - app_name="SmileyFace UT4 Server Panel", - app_author="Mathew Guest", - app_version="0.1", - *args, - **kwargs, - ) - - def _cli_options(self): - pass - - def _command_menu(self): - sm_root = self.cli.init_submenu("command") - _util.register_class_as_commands(self, sm_root, hub_machine.UT4ServerMachine) - - sm_scrape = sm_root.create_submenu("scrape") - _util.register_class_as_commands(self, sm_scrape, scrape_latest.ScrapeUt4Pugs) - - _util.register_class_as_commands(self, sm_scrape, scrape_latest.ScrapeUtcc) - - _util.register_class_as_commands(self, sm_scrape, scrape_latest.LocalFs) - - def _services(self): - self["model"] = lambda: hub_machine.UTServerMachine(self.ctx) - self.dal = datalayer.DataLayer(self.ctx) - self["dal"] = lambda: self.dal - self["datalayer"] = lambda: datalayer.DbFuncs(self.ctx, self.dal) - - # self['localfs'] = lambda: datalayer.LocalFs(self.ctx, datalayer) - - def interactive_shell(self): - pass - - def invoke_from_cli(self): - rc = self.load_command() - if not rc: - print("Invalid command. Try -h for usage") - return - # load config - self.invoke_command() - - def usage(self): - s = """ -Unreal Tournament 4 Server Build and Deploy Script - -A list of commands is shown below. - -List commands and usage: - ./ut4-server-ctl.sh - -Show help for a specific command: - ./ut4-server-ctl.sh --help - -Here is the list of sub-commands with short syntax reminder: - ./ut4-server-ctl.sh 1click-deploy - ./ut4-server-ctl.sh clean-instance - ./ut4-server-ctl.sh create-directories - ./ut4-server-ctl.sh download-linux-server - ./ut4-server-ctl.sh download-logs - ./ut4-server-ctl.sh generate-instance - ./ut4-server-ctl.sh start-server - ./ut4-server-ctl.sh stop-server - ./ut4-server-ctl.sh upload-redirects - ./ut4-server-ctl.sh upload-server - -Typical Usage: - 1) You need to either configure the remote server hostnames (both game server and remote redirect server) - in the vars files. Edit vars with a text editor, or edit the defaults in this file, or manually type - them interactively when prompted* (coming soon). - - e.g.: - PROJECT_DIR="/path/to/this/script/directory/on/local/machine" - REMOTE_GAME_HOST="54.123.456.10" - REMOTE_GAME_DIR="/home/ut4/hub-instance" - - REMOTE_REDIRECT_HOST="45.321.654.10" - REMOTE_REDIRECT_DIR="/srv/ut4-redirect/" - - 2) You need to download the latest Linux Server release from Epic. Do - this with the 'download-server' command. - - e.g.: - ./ut4-server-ctl.sh download-server - - 3) Add and configure custom maps, mutators, rulesets, and hub configuration to your *local* project folder. - This is done by modifying the files in the project directory. With the current environment variables, - this is set to be: - - "$PROJECT_DIR" - - This is the fun part! Connect with UTCC or UTZONE.DE (unaffiliated) to find custom content to put on - your hub. - - 4) 1click-deploy to update the remote hub and redirect with your latest content. You're done! Rinse and repeat. - - e.g.: - ./ut4-server-ctl.sh 1click-deploy - - Alternatively, this command is equivalent to running the separate commands. If you'd like more fine-grained - control, you can run them individually. - ./ut4-server-ctl.sh generate-instance - ./ut4-server-ctl.sh upload-redirects - ./ut4-server-ctl.sh upload-server - ./ut4-server-ctl.sh restart-server -""" - print(s) +from .cli import cli def start_app(): - app = SmileyFace() - app.invoke_from_cli() + cli() diff --git a/smileyface/cli.py b/smileyface/cli.py new file mode 100644 index 0000000..157058c --- /dev/null +++ b/smileyface/cli.py @@ -0,0 +1,145 @@ +import click + +from smileyface.context import AppContext +from smileyface.logging_setup import setup_logging +from smileyface.settings import AppSettings + + +def _make_ctx(): + """Build the application context, datalayer, and command instances.""" + from smileyface import datalayer, hub_machine, scrape_latest + + settings = AppSettings() + setup_logging() + ctx = AppContext(settings) + dal = datalayer.DataLayer(ctx) + db = datalayer.DbFuncs(ctx, dal) + machine = hub_machine.UT4ServerMachine(ctx, db) + scrape_pugs = scrape_latest.ScrapeUt4Pugs(ctx, db) + scrape_utcc = scrape_latest.ScrapeUtcc(ctx, db) + local_fs = scrape_latest.LocalFs(ctx, db) + return machine, scrape_pugs, scrape_utcc, local_fs + + +@click.group() +def cli(): + """SmileyFace UT4 Server Panel""" + pass + + +@cli.command("oneclickdeploy") +def oneclickdeploy(): + """Generate instance, upload redirects, and upload server.""" + machine, *_ = _make_ctx() + machine.oneclickdeploy() + + +@cli.command("clean-instance") +def clean_instance(): + """Deletes the generated instance on the local machine.""" + machine, *_ = _make_ctx() + machine.clean_instance() + + +@cli.command("create-directories") +def create_directories(): + """Create required directories for maps, mutators, and config.""" + machine, *_ = _make_ctx() + machine.create_directories() + + +@cli.command("download-linux-server") +def download_linux_server(): + """Download the latest Linux UT4 Server from Epic.""" + machine, *_ = _make_ctx() + machine.download_linux_server() + + +@cli.command("download-logs") +def download_logs(): + """Download the logs from the target hub.""" + machine, *_ = _make_ctx() + machine.download_logs() + + +@cli.command("generate-instance") +def generate_instance(): + """Build local server instance from current configuration.""" + machine, *_ = _make_ctx() + machine.generate_instance() + + +@cli.command("restart-server") +def restart_server(): + """Restart the UT4 server.""" + machine, *_ = _make_ctx() + machine.restart_server() + + +@cli.command("start-server") +def start_server(): + """Start the UT4 server.""" + machine, *_ = _make_ctx() + machine.start_server() + + +@cli.command("stop-server") +def stop_server(): + """Stop the UT4 server.""" + machine, *_ = _make_ctx() + machine.stop_server() + + +@cli.command("upload-redirects") +def upload_redirects(): + """Upload paks to redirect server.""" + machine, *_ = _make_ctx() + machine.upload_redirects() + + +@cli.command("upload-server") +def upload_server(): + """Upload game files to the hub server.""" + machine, *_ = _make_ctx() + machine.upload_server() + + +@cli.group("scrape") +def scrape(): + """Web scraping subcommands.""" + pass + + +@scrape.command("ut4pugs") +def scrape_ut4pugs(): + """Check ut4pugs.us for latest content.""" + _, pugs, *_ = _make_ctx() + pugs.check_ut4pugs_for_latest() + + +@scrape.command("utcc") +def scrape_utcc_cmd(): + """Check utcc.unrealpugs.com for latest content.""" + *_, utcc, _ = _make_ctx() + utcc.check_ut4cc_for_latest() + + +@scrape.command("create-db-table") +def create_db_table(): + """Create database tables.""" + *_, local_fs = _make_ctx() + local_fs.create_db_table() + + +@scrape.command("load-md5s") +def load_md5s(): + """Load MD5 checksums from local pak files.""" + *_, local_fs = _make_ctx() + local_fs.load_md5s() + + +@scrape.command("print-invalid") +def print_invalid(): + """Print pak files that failed validation.""" + *_, local_fs = _make_ctx() + local_fs.print_invalid_filepaks() diff --git a/smileyface/config.spec b/smileyface/config.spec deleted file mode 100644 index ef0b475..0000000 --- a/smileyface/config.spec +++ /dev/null @@ -1,62 +0,0 @@ -[app] -project_dir = string(max=255, default='') -config_dir = string(max=255, default='') -download_url = string(max=255, default='https://s3.amazonaws.com/unrealtournament/ShippedBuilds/%2B%2BUT%2BRelease-Next-CL-3525360/UnrealTournament-Server-XAN-3525360-Linux.zip') -download_filename = string(max=255, default='UnrealTournament-Server-XAN-3525360-Linux.zip') -download_md5 = string(max=255, default='cad730ad6793ba6261f9a341ad7396eb') -skip_validate = boolean(default=False) -redirect_protocol = string(max=255, default='') -redirect_url = string(max=255, default='') -remote_game_host = string(max=255, default='') -remote_game_dir = string(max=255, default='') -remote_redirect_host = string(max=255, default='') - -sqlite_filename = string(max=255, default='smiles.db') - -[logging] -log_file = string(max=255, default='') -log_level = option('critical', 'error', 'warning', 'info', 'debug', default='info') -log_fmt = string(max=255, default='') -disable_existing_loggers = boolean(default=False) - - [[formatters]] - [[[colored]]] - () = string(default='colorlog.ColoredFormatter') - format = string(max=255, default='%(log_color)s%(levelname)-8s%(reset)s:%(log_color)s%(name)-5s%(reset)s:%(white)s%(message)s') - - [[[basic]]] - () = string(max=255, default='logging.Formatter') - format = string(max=255, default='%(levelname)s:%(name)s:%(asctime)s:%(message)s') - - [[[forstorage]]] - () = string(max=255, default='logging.Formatter') - format = string(max=255, default='%(levelname)s:%(name)s:%(asctime)s:%(message)s') - - [[handlers]] - [[[stderr]]] - class = string(max=255, default='logging.StreamHandler') - level = option('critical', 'error', 'warning', 'info', 'debug', default='debug') - formatter = string(max=255, default='colored') - - [[[file]]] - class = string(max=255, default='logging.handlers.RotatingFileHandler') - level = option('critical', 'error', 'warning', 'info', 'debug', default='warning') - formatter = string(max=255, default='forstorage') - filename = string(max=255, default='cas_admin.log') - maxBytes = integer(min=0, max=33554432, default=33554432) - backupCount = integer(min=0, max=3, default=1) - - [[loggers]] - [[[root]]] - level = option('critical', 'error', 'warning', 'info', 'debug', default='debug') - handlers = string_list(max=8, default=list('file',)) - - [[[ut4]]] - level = option('critical', 'error', 'warning', 'info', 'debug', default='debug') - handlers = string_list(max=8, default=list('stderr',)) - propagate = boolean(default=True) - - [[[db]]] - level = option('critical', 'error', 'warning', 'info', 'debug', default='debug') - handlers = string_list(max=8, default=list('stderr',)) - propagate = boolean(default=True) diff --git a/smileyface/context.py b/smileyface/context.py new file mode 100644 index 0000000..acd6762 --- /dev/null +++ b/smileyface/context.py @@ -0,0 +1,16 @@ +import logging + +from smileyface.settings import AppSettings + + +class AppContext: + def __init__(self, settings: AppSettings): + self.settings = settings + self.log = _LoggerDict() + + +class _LoggerDict: + """Dict-like logger access: ctx.log["ut4"] -> logging.getLogger("ut4")""" + + def __getitem__(self, name: str) -> logging.Logger: + return logging.getLogger(name) diff --git a/smileyface/datalayer/datalayer.py b/smileyface/datalayer/datalayer.py index 8f9b9cb..67d9f2f 100644 --- a/smileyface/datalayer/datalayer.py +++ b/smileyface/datalayer/datalayer.py @@ -1,7 +1,7 @@ import os import sqlite3 -import appdirs +import platformdirs from smileyface import myutil @@ -18,8 +18,8 @@ class DataLayer: return self._db_conn def _create_db_connection(self): - local_db_filename = self.ctx.config["app"]["sqlite_filename"] - appdir = appdirs.user_data_dir("smileyface") + local_db_filename = self.ctx.settings.sqlite_filename + appdir = platformdirs.user_data_dir("smileyface") fullpath = os.path.join(appdir, local_db_filename) self.ctx.log["ut4"].info("sqlite3 filename: %s", fullpath) diff --git a/smileyface/datalayer/db_ops.py b/smileyface/datalayer/db_ops.py index bb5f090..d2952fc 100644 --- a/smileyface/datalayer/db_ops.py +++ b/smileyface/datalayer/db_ops.py @@ -1,8 +1,8 @@ import datetime import os -import app_skellington._util as apputil -import appdirs +from importlib.resources import files + import sqlparse from smileyface import myutil, structs @@ -14,8 +14,8 @@ class DbFuncs: self.dal = dal def create_tables(self): - sql_filename = apputil.get_asset(__name__, "create_schema.sql") - with open(sql_filename) as fp: + sql_ref = files("smileyface.datalayer").joinpath("create_schema.sql") + with sql_ref.open("r") as fp: contents_sql = fp.read() stmts = sqlparse.split(contents_sql) diff --git a/smileyface/hub_machine.py b/smileyface/hub_machine.py index 92696f3..d16b8da 100644 --- a/smileyface/hub_machine.py +++ b/smileyface/hub_machine.py @@ -8,8 +8,6 @@ import subprocess import sys import time -import configobj - from . import myutil, structs from ._util import md5sum_file from .gameconfig_edit import GameIniSpecial, UnrealIniFile @@ -28,7 +26,7 @@ class UT4ServerMachine: self.upload_redirects() self.upload_server() - def clean_instance(self, x): + def clean_instance(self): """ Deletes the generated instance on the local machine. """ @@ -41,7 +39,7 @@ class UT4ServerMachine: Create required directories which the user installs maps, mutators, and config to. """ dirs = ("base", "files/config", "files/maps", "files/mutators", "files/rulesets", "files/unused") - project_dir = self.ctx.config["app"]["project_dir"] + project_dir = self.ctx.settings.project_dir if len(project_dir.strip()) == 0: project_dir = "." print("project_dir:", project_dir) @@ -51,7 +49,7 @@ class UT4ServerMachine: cmd = "mkdir -p {}".format(fp) self._invoke_command(cmd) - def download_linux_server(self, x): + def download_linux_server(self): """ Download the latest Linux Unreal Tournament 4 Server from Epic """ @@ -61,9 +59,9 @@ class UT4ServerMachine: """ Download the logs from the target hub. """ - config_dir = self.ctx.config["app"]["config_dir"] - remote_game_host = self.ctx.config["app"]["remote_game_host"] - remote_game_dir = self.ctx.config["app"]["remote_game_dir"] + config_dir = self.ctx.settings.config_dir + remote_game_host = self.ctx.settings.remote_game_host + remote_game_dir = self.ctx.settings.remote_game_dir self.ctx.log["ut4"].info("Downloading instance logs from target hub.") cmd = """ @@ -88,7 +86,7 @@ ssh {remote_game_host} rm {remote_game_dir}/LinuxServer/UnrealTournament/Saved/L can be copied to the server. """ self.ctx.log["ut4"].info("Generating server instance from custom files...") - project_dir = self.ctx.config["app"]["project_dir"] + project_dir = self.ctx.settings.project_dir # rsync src = "/".join([project_dir, "base/LinuxServer"]) @@ -146,10 +144,10 @@ ssh {remote_game_host} {remote_game_dir}/stop-server.sh """ self.ctx.log["ut4"].info("Uploading redirects (maps, mutators, etc.) to target hub.") - project_dir = self.ctx.config["app"]["project_dir"] + project_dir = self.ctx.settings.project_dir # paks_dir = os.path.join(project_dir, 'instance/LinuxServer/UnrealTournament/Content/Paks/') paks_dir = os.path.join(project_dir, "files/") # trailing slash required - remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"] + remote_redirect_host = self.ctx.settings.remote_redirect_host cwd = project_dir cmd = """ rsync -rivz \ @@ -174,8 +172,8 @@ rsync -rivz \ self._redirect_chown() def _redirect_hide_passwords(self): - project_dir = self.ctx.config["app"]["project_dir"] - remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"] + project_dir = self.ctx.settings.project_dir + remote_redirect_host = self.ctx.settings.remote_redirect_host # (on the server): gameini = "/srv/ut4-redirect.zavage.net/config/Game.ini" engineini = "/srv/ut4-redirect.zavage.net/config/Engine.ini" @@ -197,8 +195,8 @@ ssh mathewguest.com \ self._invoke_command(cmd) def _redirect_upload_script(self): - project_dir = self.ctx.config["app"]["project_dir"] - remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"] + project_dir = self.ctx.settings.project_dir + remote_redirect_host = self.ctx.settings.remote_redirect_host cmd = """ rsync -vz \ {project_dir}/ut4-server-ctl.sh \ @@ -213,8 +211,8 @@ rsync -vz \ self._invoke_command(cmd) def _redirect_chown(self): - project_dir = self.ctx.config["app"]["project_dir"] - remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"] + project_dir = self.ctx.settings.project_dir + remote_redirect_host = self.ctx.settings.remote_redirect_host cmd = """ ssh mathewguest.com \ @@ -227,9 +225,9 @@ ssh mathewguest.com \ Upload all required game files to the hub server. """ self.ctx.log["ut4"].info("Uploading customized server") - project_dir = self.ctx.config["app"]["project_dir"] - remote_game_host = self.ctx.config["app"]["remote_game_host"] - remote_game_dir = self.ctx.config["app"]["remote_game_dir"] + project_dir = self.ctx.settings.project_dir + remote_game_host = self.ctx.settings.remote_game_host + remote_game_dir = self.ctx.settings.remote_game_dir cwd = None # transfer #1 @@ -304,7 +302,7 @@ ssh {remote_game_host} \ # Make binary executable: bin_name = "UE4Server-Linux-Shipping" - project_dir = self.ctx.config["app"]["project_dir"] + project_dir = self.ctx.settings.project_dir cwd = "{project_dir}/instance/LinuxServer/Engine/Binaries/Linux".format(project_dir=project_dir) target_file = "{cwd}/{bin_name}".format(cwd=cwd, bin_name=bin_name) @@ -325,8 +323,8 @@ ssh {remote_game_host} \ def _install_config(self): files = ("Game.ini", "Engine.ini") - project_dir = self.ctx.config["app"]["project_dir"] - config_dir = self.ctx.config["app"]["config_dir"] + project_dir = self.ctx.settings.project_dir + config_dir = self.ctx.settings.config_dir for fn in files: self.ctx.log["ut4"].info("Installing file: %s", fn) src = os.path.join(config_dir, fn) @@ -347,7 +345,7 @@ ssh {remote_game_host} \ ini._config.write(fp) def _install_paks(self): - project_dir = self.ctx.config["app"]["project_dir"] + project_dir = self.ctx.settings.project_dir self.ctx.log["ut4"].info("Installing maps...") cmd = "rsync -ravzp {src} {dst}".format( @@ -370,9 +368,9 @@ ssh {remote_game_host} \ def _install_redirect_lines(self): self.ctx.log["ut4"].info("Generating redirect references...") - redirect_protocol = self.ctx.config["app"]["redirect_protocol"] - redirect_url = self.ctx.config["app"]["redirect_url"] - project_dir = self.ctx.config["app"]["project_dir"] + redirect_protocol = self.ctx.settings.redirect_protocol + redirect_url = self.ctx.settings.redirect_url + project_dir = self.ctx.settings.project_dir mod_dir = "/".join([project_dir, "files"]) game_ini_filepath = "/".join( @@ -415,7 +413,7 @@ ssh {remote_game_host} \ def _install_rulesets(self): self.ctx.log["ut4"].info("Concatenating rulesets for game modes...") - project_dir = self.ctx.config["app"]["project_dir"] + project_dir = self.ctx.settings.project_dir src_dir = "/".join([project_dir, "files/rulesets"]) out_dir = "/".join([project_dir, "/instance/LinuxServer/UnrealTournament/Saved/Config/Rulesets"]) @@ -464,7 +462,7 @@ ssh {remote_game_host} \ "remote_redirect_host", ) for name in variable_names: - value = self.ctx.config["app"][name] + value = getattr(self.ctx.settings, name) self.ctx.log["ut4"].info("%s: %s", name, value) i = input("Continue with above configuration? (y/N):") diff --git a/smileyface/logging_setup.py b/smileyface/logging_setup.py new file mode 100644 index 0000000..6a46571 --- /dev/null +++ b/smileyface/logging_setup.py @@ -0,0 +1,41 @@ +import logging +import logging.config + + +def setup_logging(log_file: str = ""): + config = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "colored": { + "format": "%(levelname)-8s:%(name)-5s:%(message)s", + }, + "basic": { + "format": "%(levelname)s:%(name)s:%(asctime)s:%(message)s", + }, + }, + "handlers": { + "stderr": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "colored", + }, + }, + "loggers": { + "ut4": {"level": "DEBUG", "handlers": ["stderr"], "propagate": True}, + "db": {"level": "DEBUG", "handlers": ["stderr"], "propagate": True}, + }, + "root": {"level": "DEBUG", "handlers": []}, + } + if log_file: + config["handlers"]["file"] = { + "class": "logging.handlers.RotatingFileHandler", + "level": "WARNING", + "formatter": "basic", + "filename": log_file, + "maxBytes": 33554432, + "backupCount": 1, + } + config["root"]["handlers"].append("file") + + logging.config.dictConfig(config) diff --git a/smileyface/scrape_latest/local_fs.py b/smileyface/scrape_latest/local_fs.py index dfe1b27..808e55b 100644 --- a/smileyface/scrape_latest/local_fs.py +++ b/smileyface/scrape_latest/local_fs.py @@ -13,7 +13,7 @@ class LocalFs: self.datalayer.create_tables() def load_md5s(self): - paks_dir = self.ctx.config["app"]["project_dir"] + paks_dir = self.ctx.settings.project_dir maps_dir = os.path.join(paks_dir, "files", "maps") print(maps_dir) self._load_md5_one_dir(maps_dir) diff --git a/smileyface/settings.py b/smileyface/settings.py new file mode 100644 index 0000000..f6f39f1 --- /dev/null +++ b/smileyface/settings.py @@ -0,0 +1,22 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class AppSettings(BaseSettings): + model_config = SettingsConfigDict( + env_prefix="SMILEYFACE_", + env_file=".env", + env_file_encoding="utf-8", + ) + + project_dir: str = "" + config_dir: str = "" + download_url: str = "" + download_filename: str = "" + download_md5: str = "" + skip_validate: bool = False + redirect_protocol: str = "" + redirect_url: str = "" + remote_game_host: str = "" + remote_game_dir: str = "" + remote_redirect_host: str = "" + sqlite_filename: str = "smiles.db"