Implement config storage

This commit is contained in:
Logan Cusano
2025-05-11 20:36:31 -04:00
parent 52965f2a30
commit f17f7fc36a
5 changed files with 154 additions and 16 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,4 @@
*.venv
*__pycache__
*__pycache__
*.bat
*.json

View File

@@ -4,6 +4,9 @@ FROM python:3.13-slim
# Set the working directory in the container
WORKDIR /app
# Create the data dir (this should be a volume on the local machine to store data)
RUN mkdir -p data
# Copy the requirements file into the container
COPY requirements.txt .

View File

@@ -14,5 +14,6 @@ run: build
-e SERVER_WS_URI=${SERVER_WS_URI} \
-e SERVER_API_URL=${SERVER_API_URL} \
-e CLIENT_API_URL=${CLIENT_API_URL} \
-v ./data:/data \
--network=host \
$(CLIENT_IMAGE)

View File

@@ -7,14 +7,19 @@ from drb_cdb_api import DRBCDBAPI
from drb_cdb_types import ConfigGenerator
from server_api import RadioAPIClient
from enum import Enum
from config import Config
app_conf = Config()
# --- Client Configuration ---
SERVER_WS_URI = os.getenv("SERVER_WS_URI", "ws://localhost:8765")
SERVER_API_URL = os.getenv("SERVER_API_URL", "http://localhost:5000")
CLIENT_API_URL = os.getenv("CLIENT_API_URL", "http://localhost:8001")
# Generate or define a unique ID for THIS client instance
# In a real app, this might come from config or a login process
CLIENT_ID = f"client-{uuid.uuid4().hex[:8]}" # TODO - Implement persistent ID
# Get/set the ID of this node
if not app_conf.get("client_id"):
app_conf.set("client_id", f"client-{uuid.uuid4().hex[:8]}")
CLIENT_ID = app_conf.client_id
# ----------------------------
# Dictionary mapping command names (strings) to local client functions
@@ -38,7 +43,6 @@ def command(func):
return func
# --- Define Client-Side Command Handlers (The "API" functions) ---
# Join server
@command
async def join_server(system_id, guild_id, channel_id):
@@ -114,13 +118,6 @@ async def leave_server(guild_id):
print("Leave server completed")
@command
async def set_status(status_text):
"""Example command: Sets or displays a status."""
print(f"\n--- Server Command: set_status ---")
print(f"Status updated to: {status_text}")
print("----------------------------------")
@command
async def run_task(task_id, duration_seconds):
"""Example command: Simulates running a task."""
@@ -130,11 +127,7 @@ async def run_task(task_id, duration_seconds):
print(f"Task {task_id} finished.")
print("------------------------------")
# Add more command handlers as needed...
# ------------------------------------------------------------------
async def receive_commands(websocket):
"""Listens for and processes command messages from the server."""
async for message in websocket:

139
app/config.py Normal file
View File

@@ -0,0 +1,139 @@
import os
import json
class Config:
"""
Manages application configuration stored in a JSON file.
Provides attribute-style access to configuration values.
"""
def __init__(self, file_path='/data/config.json'):
"""
Initializes the Config manager.
Loads configuration from the JSON file. Creates the file with an
empty JSON object if it doesn't exist.
Args:
file_path (str): The path to the JSON configuration file.
"""
self.file_path = file_path
self._config_data = {} # Internal dictionary to hold config data
self._load_config()
def _load_config(self):
"""Loads configuration key-value pairs from the JSON file."""
# Check if the file exists. If not, create it with an empty JSON object.
if not os.path.exists(self.file_path):
self._save_config() # Create the file with empty data
try:
with open(self.file_path, 'r') as f:
# Load data from the file. Handle empty file case.
content = f.read()
if content:
self._config_data = json.loads(content)
else:
self._config_data = {} # Initialize as empty if file is empty
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Error loading config file {self.file_path}: {e}")
# If there's an error loading, initialize with empty data
self._config_data = {}
# Optionally, you might want to back up the problematic file
# or log the error more severely in a real application.
def _save_config(self):
"""Saves the current configuration data to the JSON file."""
try:
with open(self.file_path, 'w') as f:
json.dump(self._config_data, f, indent=4) # Use indent for readability
except IOError as e:
print(f"Error saving config file {self.file_path}: {e}")
except TypeError as e:
print(f"Error serializing config data to JSON: {e}. Ensure all values are JSON serializable.")
def get(self, key, default=None):
"""
Retrieves a configuration value by key.
Args:
key (str): The configuration key.
default: The value to return if the key is not found.
Returns:
The configuration value or the default value if not found.
"""
return self._config_data.get(key, default)
def set(self, key, value):
"""
Sets or updates a configuration value in memory and saves to the file.
Args:
key (str): The configuration key.
value: The configuration value. Must be JSON serializable.
"""
self._config_data[key] = value
self._save_config()
def delete(self, key):
"""
Deletes a configuration key-value pair from memory and saves to the file.
Args:
key (str): The configuration key to delete.
"""
if key in self._config_data:
del self._config_data[key]
self._save_config()
else:
print(f"Warning: Key '{key}' not found in config.")
def __getattr__(self, name):
"""
Allows accessing configuration values using attribute notation (e.g., config.my_key).
Args:
name (str): The attribute name (configuration key).
Returns:
The configuration value associated with the key.
Raises:
AttributeError: If the key is not found in the configuration.
"""
if name in self._config_data:
return self._config_data[name]
# Fallback for standard attributes if not found in config data
try:
return self.__getattribute__(name)
except AttributeError:
raise AttributeError(f"'Config' object has no attribute '{name}' and key '{name}' not found in config.")
def __setattr__(self, name, value):
"""
Allows setting configuration values using attribute notation (e.g., config.my_key = value).
Args:
name (str): The attribute name (configuration key).
value: The value to set. Must be JSON serializable.
"""
# Handle setting internal attributes like file_path or _config_data
if name in ('file_path', '_config_data'):
super().__setattr__(name, value)
else:
# Treat other attribute assignments as setting config values
self.set(name, value)
def __delattr__(self, name):
"""
Allows deleting configuration values using attribute notation (e.g., del config.my_key).
Args:
name (str): The attribute name (configuration key) to delete.
"""
if name in self._config_data:
self.delete(name)
else:
# Fallback for standard attributes
super().__delattr__(name)