Skip to content

Add TOML configuration file support for server settings (#248)#261

Merged
from2001 merged 16 commits intodevelopfrom
feature/248-config-file-support
Jan 18, 2026
Merged

Add TOML configuration file support for server settings (#248)#261
from2001 merged 16 commits intodevelopfrom
feature/248-config-file-support

Conversation

@hacha
Copy link
Collaborator

@hacha hacha commented Jan 7, 2026

Summary

  • Add --config CLI option to specify TOML configuration file
  • Configuration priority: CLI args > config file > defaults
  • Use Python 3.11+ built-in tomllib for TOML parsing
  • Add comprehensive sample configuration file with documentation

Changes

  • New: src/styly_netsync/config.py - Configuration management module with ServerConfig dataclass
  • New: tests/test_config.py - 47 tests for configuration functionality (93% coverage)
  • New: examples/config.toml - Sample configuration file with all settings documented
  • Modified: src/styly_netsync/server.py - Integrate config file support
  • Modified: src/styly_netsync/binary_serializer.py - Add configurable max_virtual_transforms

Configuration Sections

Section Settings
[network] dealer_port, pub_port, server_discovery_port, server_name, enable_server_discovery
[timing] base_broadcast_interval, idle_broadcast_interval, dirty_threshold, client_timeout, cleanup_interval, device_id_expiry_time, status_log_interval, main_loop_sleep, poll_timeout
[network_variables] max_global_vars, max_client_vars, max_var_name_length, max_var_value_length, nv_flush_interval, nv_monitor_window_size, nv_monitor_threshold
[limits] max_virtual_transforms, pub_queue_maxsize, delta_ring_size
[logging] log_dir, log_level_console, log_json_console, log_rotation, log_retention

Usage

styly-netsync-server --config examples/config.toml

Test plan

  • All 82 tests pass
  • Config file loading works correctly
  • CLI args override config file values
  • Validation catches invalid values
  • Backward compatible (works without config file)

Closes #248

🤖 Generated with Claude Code

@hacha hacha requested a review from from2001 January 7, 2026 09:52
@from2001
Copy link
Collaborator

from2001 commented Jan 7, 2026

@claude Review this PR.

@claude
Copy link
Contributor

claude bot commented Jan 7, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@hacha hacha force-pushed the feature/248-config-file-support branch from 902d46d to 10bd1f7 Compare January 14, 2026 03:21
@from2001
Copy link
Collaborator

@claude Review the code.

@claude
Copy link
Contributor

claude bot commented Jan 14, 2026

Claude finished @from2001's task in 2m 6s —— View job


Code Review Complete ✅

I've completed a thorough review of the TOML configuration file support implementation. Overall, this is a well-designed and well-tested feature with strong architecture. Here are my findings:


✅ Strengths

1. Excellent Architecture

  • Clean separation of concerns with dedicated config.py module
  • Proper configuration layering: CLI args > user config > defaults
  • Type-safe dataclass-based configuration with ServerConfig
  • Good use of Python 3.11+ built-in tomllib (no external dependencies)

2. Comprehensive Test Coverage

  • 47 tests with 93% coverage is excellent
  • Tests cover edge cases: validation, empty files, partial configs, unknown keys
  • Good error handling test coverage

3. User Experience

  • Helpful unknown key detection warns about typos (config.py:410-418)
  • Clear error messages with ConfigurationError exception
  • Example config file with good documentation
  • Backward compatible (works without config file)

4. Code Quality

  • Clean, readable code with proper type hints
  • Good documentation and docstrings
  • Follows Python best practices

🔍 Issues Found

Critical Issues

None identified - the implementation is solid.

Medium Priority

1. Using print() for warnings in config loading (config.py:412-418)

The code uses print() to display warnings about unknown config keys. This happens before logging is configured, which is why print() is used. However, this creates inconsistency.

Location: src/styly_netsync/config.py:412-418

print(f"WARNING: Unknown keys in {user_config_path}:")
for section, keys in unknown.items():
    if "_unknown_section" in keys:
        print(f"  - Unknown section: [{section}]")
    else:
        for key in keys:
            print(f"  - [{section}] unknown key: {key}")

Recommendation: This is acceptable given the constraint that logging isn't configured yet. However, consider:

  1. Writing to sys.stderr instead of stdout for warnings
  2. Adding a comment explaining why print() is used here
  3. Or: Return warnings from create_config_from_args() and let the caller log them after configuring logging

2. CLI argument name inconsistency

The CLI uses --user-config but internally it's stored as args.user_config. According to the PR description summary, it should be --config for brevity.

Location: src/styly_netsync/server.py:1925

parser.add_argument(
    "--user-config",  # Should this be --config?
    type=Path,
    metavar="FILE",
    help="Path to user TOML configuration file (overrides defaults)",
)

Recommendation: The PR summary says --config but the code uses --user-config. Consider:

  • If --config was intended, update the argument name
  • If --user-config is intentional, update the PR description to match

3. Missing validation for conflicting timing values

The validation checks individual timing values are positive, but doesn't validate logical relationships between them.

Example edge case:

[timing]
base_broadcast_interval = 1.0
dirty_threshold = 2.0  # This should be <= base_broadcast_interval

Recommendation: Add cross-field validation to ensure:

  • dirty_threshold <= base_broadcast_interval
  • idle_broadcast_interval >= base_broadcast_interval

This could prevent configuration mistakes that lead to unexpected behavior.

Low Priority

4. Type annotation for empty string conversion could be clearer

In flatten_toml_config(), the conversion of empty strings to None is done inline (config.py:191-193):

if key in ("log_dir", "log_rotation", "log_retention"):
    if value == "":
        value = None

Recommendation: This is fine, but a comment explaining why TOML empty strings need conversion would help future maintainers.

5. Missing type validation before returning ServerConfig

The flatten_toml_config() returns dict[str, Any] which is then unpacked into ServerConfig(**flat_data). If the TOML file has wrong types (e.g., string instead of int), the TypeError from dataclass construction might be cryptic.

Location: src/styly_netsync/config.py:331

Recommendation: Consider catching TypeError in load_default_config() and re-raising with a more helpful message (which you already do on line 336!). Good job! But consider doing the same in create_config_from_args() when applying user config overrides (line 424).

6. The --no-server-discovery boolean flag handling

The logic in merge_cli_args() only sets enable_server_discovery = False when the flag is present and True (config.py:361-362). This is correct, but the variable name no_server_discovery is slightly confusing since it's a boolean, not a flag.

Current code:

if hasattr(args, "no_server_discovery") and args.no_server_discovery:
    updates["enable_server_discovery"] = False

Recommendation: This is correct but consider a comment explaining that no_server_discovery is only checked when True (since False means the user didn't use the flag).


🔒 Security Assessment

✅ No security issues identified:

  • Input validation is comprehensive (ports, timing values, log levels)
  • File paths are properly handled with Path objects
  • No SQL injection, command injection, or path traversal risks
  • TOML parsing uses built-in tomllib (safe)

🧪 Testing Assessment

✅ Excellent test coverage:

  • All validation rules tested
  • Edge cases covered (empty files, missing files, invalid TOML)
  • Configuration layering tested
  • Error handling tested
  • 93% coverage is very good

Minor suggestion: Add a test for the logging-related warnings from get_unknown_keys() to ensure they work as expected.


📋 Code Style & Best Practices

✅ Code follows Python best practices:

  • Type hints throughout
  • Proper exception handling
  • Clear naming conventions
  • Good documentation
  • Follows existing codebase patterns

🔄 Backward Compatibility

✅ Fully backward compatible:

  • All existing CLI args still work
  • Server runs without config file
  • Default values unchanged (moved to default.toml)
  • No breaking changes to API

📝 Documentation

✅ Good documentation:

  • examples/config.toml has clear comments
  • default.toml well-documented
  • Docstrings in config.py are comprehensive
  • CLI help text is clear

Suggestion: Consider adding a section to the main README.md documenting the configuration file feature (if not already present).


🎯 Recommendations Summary

Must Fix (None)

No critical issues requiring fixes.

Should Fix

  1. Consider renaming --user-config to --config (or update PR description) for consistency
  2. Use sys.stderr for warnings instead of stdout (config.py:412-418)
  3. Add cross-field validation for timing values to prevent logical inconsistencies

Nice to Have

  1. Add comment explaining empty string → None conversion
  2. Add test for unknown key warnings
  3. Consider adding README documentation for the config file feature

✅ Approval Recommendation

This PR is ready to merge after addressing the "Should Fix" items above, particularly #1 (CLI argument naming consistency). The implementation is solid, well-tested, and adds valuable functionality without breaking changes.

Great work on this feature! The configuration architecture is clean and extensible. 🎉


Branch: feature/248-config-file-support

@from2001
Copy link
Collaborator

@claude Explain about the TOML configuration based on the latest code.

@claude
Copy link
Contributor

claude bot commented Jan 14, 2026

Claude finished @from2001's task in 1m 58s —— View job


TOML Configuration Explanation

This PR adds comprehensive TOML-based configuration file support for the STYLY NetSync Server. Below is a complete explanation of the feature based on the latest code.


📋 Overview

The TOML configuration feature allows you to customize server settings using configuration files instead of only relying on CLI arguments. This makes it easier to manage different server deployments and maintain consistent configurations across environments.

Key Features:

  • ✅ TOML-based configuration format (using Python 3.11+ built-in tomllib)
  • ✅ Layered configuration system with clear precedence rules
  • ✅ Comprehensive validation with helpful error messages
  • ✅ Backward compatible (works without config files)
  • ✅ Unknown key detection to catch typos
  • ✅ 47 tests with 93% coverage

🎯 Configuration Priority

The configuration system uses a three-layer priority model:

CLI Arguments > User Config File > Default Config
   (highest)                          (lowest)

Example:

  • default.toml sets dealer_port = 5555
  • Your config file sets dealer_port = 6000
  • CLI arg --dealer-port 7000 is provided
  • Result: Server uses port 7000 (CLI wins)

This allows you to:

  1. Define sensible defaults in default.toml (bundled with package)
  2. Override specific settings in your user config file
  3. Override again with CLI args for quick testing

📁 Configuration File Structure

Configuration files use TOML format with five main sections:

1. [network] - Network Settings

[network]
dealer_port = 5555              # ZeroMQ DEALER port for client-server communication
pub_port = 5556                 # ZeroMQ PUB port for server-to-client broadcasts
server_discovery_port = 9999    # UDP port for server discovery service
server_name = "STYLY-NetSync-Server"  # Server name announced via discovery
enable_server_discovery = true  # Enable/disable UDP discovery service

2. [timing] - Timing Settings

[timing]
base_broadcast_interval = 0.1   # Base broadcast interval in seconds (10Hz default)
idle_broadcast_interval = 0.5   # Broadcast interval when idle (2Hz default)
dirty_threshold = 0.05          # Min broadcast interval when very active (20Hz max)
client_timeout = 1.0            # Client disconnect timeout in seconds
cleanup_interval = 1.0          # Cleanup interval for removing stale data
device_id_expiry_time = 300.0   # Device ID mapping expiry (5 minutes default)
status_log_interval = 10.0      # Status log interval in seconds
main_loop_sleep = 0.02          # Main loop sleep interval (50Hz default)
poll_timeout = 100              # ZeroMQ poll timeout in milliseconds

3. [network_variables] - Network Variable Settings

[network_variables]
max_global_vars = 100           # Maximum global variables per room
max_client_vars = 100           # Maximum client variables per client
max_var_name_length = 64        # Maximum variable name length in bytes
max_var_value_length = 1024     # Maximum variable value length in bytes
nv_flush_interval = 0.05        # Network variable flush interval (50ms default)
nv_monitor_window_size = 1.0    # NV monitoring window size in seconds
nv_monitor_threshold = 200      # NV requests per second before warning

4. [limits] - Internal Limits

[limits]
max_virtual_transforms = 50     # Maximum virtual transforms per client
pub_queue_maxsize = 10000       # PUB queue maximum size
delta_ring_size = 10000         # Delta ring buffer size for NV synchronization

5. [logging] - Logging Settings

[logging]
log_dir = ""                    # Directory for log files (empty = console-only)
log_level_console = "INFO"      # Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
log_json_console = false        # Enable JSON format for console logs
log_rotation = ""               # Log rotation rule (loguru syntax, e.g., "10 MB")
log_retention = ""              # Log retention rule (loguru syntax, e.g., "30 days")

🚀 Usage

Basic Usage

# Run with default configuration (no config file needed)
styly-netsync-server

# Run with custom configuration file
styly-netsync-server --user-config /path/to/config.toml

# Override specific settings via CLI
styly-netsync-server --user-config config.toml --log-level-console DEBUG

# Disable discovery service
styly-netsync-server --no-server-discovery

Creating a Configuration File

You only need to include settings you want to change:

Example: my-server.toml

# Minimal config - only override what you need
[network]
server_name = "Production-Server-01"

[logging]
log_dir = "/var/log/styly-netsync"
log_level_console = "INFO"

Example: High-Performance Config

[timing]
base_broadcast_interval = 0.05  # 20Hz instead of 10Hz
dirty_threshold = 0.025         # Allow bursts up to 40Hz

[network_variables]
max_global_vars = 200           # More variables for complex experiences

Example: Development Config

[logging]
log_level_console = "DEBUG"
log_json_console = false

[network]
server_name = "Dev-Server"

🏗️ Architecture & Implementation

Core Components

1. config.py - Configuration Management Module

Location: src/styly_netsync/config.py

Key Classes:

  • ServerConfig (dataclass) - Type-safe configuration container with all settings
  • ConfigurationError - Raised when validation fails
  • DefaultConfigError - Fatal error when default.toml cannot be loaded

Key Functions:

  • load_default_config() - Loads bundled default.toml from package resources
  • load_config_from_toml(path) - Loads user TOML file
  • flatten_toml_config(toml_data) - Converts nested TOML sections to flat dict
  • merge_cli_args(config, args) - Applies CLI argument overrides
  • create_config_from_args(args) - Main entry point that orchestrates loading
  • validate_config(config) - Validates all configuration values
  • get_unknown_keys(toml_data) - Detects typos/unknown keys

2. default.toml - Default Configuration

Location: src/styly_netsync/default.toml

This file is bundled with the package and contains all default values. It's loaded automatically using importlib.resources so it works even when the package is installed from a wheel.

3. Server Integration - server.py

Location: src/styly_netsync/server.py:1922-2049

The main() function:

  1. Parses CLI arguments (line 1923-1978)
  2. Loads configuration with create_config_from_args(args) (line 1983)
  3. Handles errors (DefaultConfigError, FileNotFoundError, ConfigurationError)
  4. Configures logging using config values (line 2002-2010)
  5. Logs loaded configuration (line 2013-2040)
  6. Creates NetSyncServer instance with config (line 2042-2049)

Configuration Loading Flow

graph TD
    A[main starts] --> B[Parse CLI args]
    B --> C[create_config_from_args]
    C --> D[Load default.toml]
    D --> E{User config specified?}
    E -->|Yes| F[Load user TOML file]
    E -->|No| G[Use defaults only]
    F --> H[Detect unknown keys]
    H --> I[Merge user overrides]
    I --> J[Merge CLI overrides]
    G --> J
    J --> K[Validate configuration]
    K --> L{Valid?}
    L -->|No| M[Raise ConfigurationError]
    L -->|Yes| N[Return ServerConfig]
    N --> O[Configure logging]
    O --> P[Create server]
Loading

Code Flow (config.py:382-434):

def create_config_from_args(args: argparse.Namespace) -> ServerConfig:
    # Step 1: Load default configuration (required)
    config = load_default_config()
    
    # Step 2: Override with user config if specified
    if hasattr(args, "user_config") and args.user_config is not None:
        user_config_path = Path(args.user_config)
        toml_data = load_config_from_toml(user_config_path)
        
        # Warn about unknown keys (possible typos)
        unknown = get_unknown_keys(toml_data)
        if unknown:
            print(f"WARNING: Unknown keys in {user_config_path}:")
            # ... print warnings
        
        flat_data = flatten_toml_config(toml_data)
        if flat_data:
            config = dataclass_replace(config, **flat_data)
    
    # Step 3: Apply CLI overrides (highest priority)
    config = merge_cli_args(config, args)
    
    # Step 4: Validate final configuration
    errors = validate_config(config)
    if errors:
        raise ConfigurationError(errors)
    
    return config

✅ Validation

The configuration system includes comprehensive validation (config.py:234-307):

Port Validation

  • All ports must be between 1-65535
  • Validates: dealer_port, pub_port, server_discovery_port

Timing Validation

  • All timing values must be positive (> 0)
  • Validates: base_broadcast_interval, idle_broadcast_interval, dirty_threshold, client_timeout, etc.

Network Variable Limits

  • All NV limits must be positive integers
  • Validates: max_global_vars, max_client_vars, max_var_name_length, etc.

Internal Limits

  • All internal limits must be positive integers
  • Validates: max_virtual_transforms, pub_queue_maxsize, delta_ring_size

Logging Validation

  • log_level_console must be one of: DEBUG, INFO, WARNING, ERROR, CRITICAL

Example Validation Error:

$ styly-netsync-server --user-config bad.toml
ERROR: Configuration validation failed:
  - dealer_port must be between 1 and 65535, got 99999
  - base_broadcast_interval must be positive, got -0.1
  - log_level_console must be one of ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], got TRACE

🔍 Unknown Key Detection

The system warns you about typos in your config file (config.py:199-231, 409-418):

Example:

[network]
dealler_port = 6000  # Typo: "dealler" instead of "dealer"

[timming]  # Typo: "timming" instead of "timing"
base_broadcast_interval = 0.05

Output:

WARNING: Unknown keys in config.toml:
  - Unknown section: [timming]
  - [network] unknown key: dealler_port

This helps catch configuration errors early before the server starts.


🛠️ CLI Arguments

All CLI arguments still work and override config file values:

# Logging options
--log-dir /var/log/styly           # Enable file logging
--log-level-console DEBUG          # Set console log level
--log-json-console                 # Emit JSON logs
--log-rotation "100 MB"            # Custom rotation rule
--log-retention "30 days"          # Custom retention rule

# Network options
--no-server-discovery              # Disable UDP discovery
--server-discovery-port 9999       # Set discovery port

# General options
--user-config /path/to/config.toml # Specify config file
-V, --version                      # Show version

📦 Example Configurations

Production Server

production.toml

[network]
server_name = "Production-LBE-Server-01"
enable_server_discovery = true

[timing]
base_broadcast_interval = 0.05  # High performance: 20Hz
client_timeout = 2.0            # Tolerate occasional network issues

[logging]
log_dir = "/var/log/styly-netsync"
log_level_console = "INFO"
log_rotation = "50 MB"
log_retention = "30 days"

Development Server

dev.toml

[network]
server_name = "Dev-Server"

[logging]
log_level_console = "DEBUG"
log_json_console = false

High-Capacity Server

high-capacity.toml

[network_variables]
max_global_vars = 500
max_client_vars = 200

[limits]
max_virtual_transforms = 100
pub_queue_maxsize = 50000
delta_ring_size = 50000

🧪 Testing

The configuration system has extensive test coverage:

Test File: tests/test_config.py

  • 47 tests covering all functionality
  • 93% code coverage for config.py
  • Tests include:
    • Default config loading
    • User config loading and merging
    • CLI argument overrides
    • Validation (ports, timing, NV limits, logging)
    • Error handling (missing files, invalid TOML, validation errors)
    • Unknown key detection
    • Edge cases (empty files, partial configs, type mismatches)

Run tests:

cd STYLY-NetSync-Server
pytest tests/test_config.py -v
pytest --cov=src/styly_netsync/config.py tests/test_config.py

🔧 Developer API

If you're integrating with the server programmatically:

from styly_netsync.config import (
    ServerConfig,
    load_default_config,
    create_config_from_args,
    validate_config,
    ConfigurationError
)

# Load default configuration
config = load_default_config()

# Create config from CLI args (handles layering automatically)
import argparse
args = argparse.Namespace(user_config=Path("config.toml"))
config = create_config_from_args(args)

# Validate configuration
errors = validate_config(config)
if errors:
    raise ConfigurationError(errors)

# Access configuration values
print(f"Server will use port {config.dealer_port}")

📚 Files Changed

File Type Description
src/styly_netsync/config.py NEW Configuration management module (434 lines)
src/styly_netsync/default.toml NEW Default configuration (bundled with package)
examples/config.toml NEW Example user configuration with documentation
tests/test_config.py NEW Comprehensive test suite (811 lines, 47 tests)
src/styly_netsync/server.py MODIFIED Integration with config system
src/styly_netsync/binary_serializer.py MODIFIED Configurable max_virtual_transforms
pyproject.toml MODIFIED Added tomli-w dev dependency for testing

🎓 Key Design Decisions

  1. Uses Python 3.11+ built-in tomllib - No external dependencies for TOML parsing
  2. Dataclass-based config - Type-safe with IDE autocomplete support
  3. Immutable updates - Uses dataclass_replace() instead of mutation
  4. Bundled defaults - default.toml shipped with package via importlib.resources
  5. Validation before startup - Catches errors early with helpful messages
  6. Unknown key detection - Warns about typos without failing
  7. Backward compatible - Works without config files (all defaults provided)

✨ Summary

The TOML configuration feature provides a robust, flexible, and user-friendly way to configure the STYLY NetSync Server. It balances ease-of-use (sensible defaults) with flexibility (full customization) while maintaining backward compatibility and providing excellent error messages.

Benefits:

  • 📝 Easy to read and edit configuration files
  • 🔧 Fine-grained control over all server settings
  • 🎯 Clear precedence rules (CLI > User Config > Defaults)
  • ✅ Comprehensive validation with helpful errors
  • 🔍 Typo detection for unknown keys
  • 🧪 Excellent test coverage (93%)
  • 📦 Zero external dependencies (uses stdlib tomllib)

Branch: feature/248-config-file-support

hacha and others added 12 commits January 18, 2026 17:39
Introduce configuration file support for STYLY NetSync Server.
This commit adds:

- New config.py module with ServerConfig dataclass
- --config CLI option to specify TOML configuration file
- Network settings: dealer_port, pub_port, server_discovery_port,
  server_name, enable_server_discovery
- Configuration validation and CLI argument merging
- Comprehensive unit tests for configuration module

Configuration priority: CLI args > config file > defaults

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend configuration file support with timing and performance settings:

- base_broadcast_interval, idle_broadcast_interval, dirty_threshold
- client_timeout, cleanup_interval, device_id_expiry_time
- status_log_interval, main_loop_sleep, poll_timeout

NetSyncServer now accepts a ServerConfig object and uses timing values
from configuration instead of class constants. Backward compatibility
is maintained when config is not provided.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend configuration with NetworkVariable limit and monitoring settings:

- max_global_vars, max_client_vars: Variable count limits
- max_var_name_length, max_var_value_length: Size limits
- nv_flush_interval: Flush cadence configuration
- nv_monitor_window_size, nv_monitor_threshold: Monitoring settings

NetSyncServer now reads NV limits and monitoring parameters from config.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend configuration with internal system limits:

- max_virtual_transforms: Maximum virtual transforms per client
- pub_queue_maxsize: Publisher queue size limit
- delta_ring_size: NV sync delta ring buffer size

Add set/get_max_virtual_transforms() functions to binary_serializer.py
for dynamic configuration of the virtual transform limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add logging section to TOML configuration with:
  - log_dir: Directory for log files (None = console only)
  - log_level_console: Console log level (default: INFO)
  - log_json_console: Enable JSON console output
  - log_rotation: Custom rotation rule (None = use internal default)
  - log_retention: Custom retention rule (None = use internal default)
- Add validation for log_level_console (must be valid log level)
- Add CLI args merging for logging settings
- Refactor main() to configure logging after config loading
  so that config file settings can be applied
- Add comprehensive tests for logging configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add examples/config.toml with all configurable settings
- Include comprehensive comments explaining each setting
- Show default values and usage examples
- Document configuration priority: CLI args > config file > defaults

Usage:
  styly-netsync-server --config examples/config.toml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change argparse default from "INFO" to None so that CLI args only
override config file values when explicitly specified by the user.

Previously, argparse default="INFO" meant args.log_level_console was
never None, so merge_cli_args always overwrote the config file value.

Add regression test to verify None CLI values preserve config settings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace SystemExit(1) with typed ConfigurationError exception that
stores the list of validation errors. This allows main() to catch
validation errors specifically and print user-friendly messages.

- Add ConfigurationError class with errors attribute
- Update create_config_from_args to raise ConfigurationError
- Update main() to catch ConfigurationError and print each error
- Add tests for ConfigurationError behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add get_unknown_keys() function to detect unknown sections and keys
in TOML configuration files. This helps catch typos like "log_levl_console"
that would otherwise be silently ignored.

- Add get_unknown_keys() function to detect unknown sections/keys
- Warn about unknown keys in create_config_from_args (using print()
  since logging is not yet configured at that point)
- Add comprehensive tests for unknown key detection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Log the "Loaded configuration from" message after configure_logging()
is called, ensuring the log output uses the properly configured
logging settings rather than loguru's defaults.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add bundled default.toml with all default configuration values
- Remove hardcoded defaults from ServerConfig dataclass
- Remove DEFAULT_* global constants from server.py
- Remove _DEFAULT_* class constants from NetSyncServer
- NetSyncServer now loads default.toml when config is not provided
- Implement layered config: CLI args > user.toml > default.toml
- Rename --config to --user-config for clarity
- Add DefaultConfigError for fatal config loading failures
- Exit with error if default.toml cannot be loaded

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Simplify configuration files by removing [network], [timing],
[network_variables], [limits], and [logging] sections. All config
keys are now at the top level, making the format simpler to read
and edit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hacha hacha force-pushed the feature/248-config-file-support branch from 10bd1f7 to 48db2cd Compare January 18, 2026 08:39
@from2001
Copy link
Collaborator

@claude Review the latest code.

@claude
Copy link
Contributor

claude bot commented Jan 18, 2026

Claude finished @from2001's task in 2m 36s —— View job


Code Review Complete ✅

I've reviewed the latest code in this PR. The implementation has significantly improved since the last review with several architectural refinements.


📊 Summary of Changes Since Last Review

The PR has undergone major improvements based on the commit history:

  1. Removed TOML section structure - Switched from nested sections ([network], [timing], etc.) to a flat configuration structure
  2. Centralized defaults - All default values now live in default.toml (bundled with package)
  3. Fixed CLI override bug - --log-level-console now correctly overrides config file values
  4. Added unknown key detection - Warns users about typos in config files
  5. Enhanced error handling - Added ConfigurationError and DefaultConfigError exceptions

✅ What's Excellent

1. Simplified Architecture (Flat Config)

The switch from nested TOML sections to a flat structure is a great design decision:

  • Before: [network] section with dealer_port underneath
  • Now: Flat dealer_port = 5555 at top level

This is simpler, cleaner, and easier to maintain. The code in config.py is much more straightforward without nested section handling.

2. Robust Error Handling

# config.py:19-38
class ConfigurationError(Exception):
    """Raised when configuration validation fails."""
    def __init__(self, errors: list[str]) -> None:
        self.errors = errors
        super().__init__(f"Configuration validation failed: {'; '.join(errors)}")

class DefaultConfigError(Exception):
    """Raised when default configuration cannot be loaded."""

Clear exception hierarchy with helpful error messages. The server gracefully handles missing/invalid config files.

3. Unknown Key Detection

# config.py:191-206, 384-389
unknown = get_unknown_keys(toml_data)
if unknown:
    print(f"WARNING: Unknown keys in {user_config_path}:")
    for key in unknown:
        print(f"  - {key}")

This catches typos early! If a user writes dealler_port instead of dealer_port, they'll get a warning.

4. Comprehensive Test Coverage

  • 47 tests covering all functionality
  • Tests for validation, config merging, CLI overrides, edge cases
  • 767 lines of test code - very thorough

5. Proper Configuration Layering

# config.py:376-405
config = load_default_config()           # Step 1: Defaults
config = apply_user_overrides(config)    # Step 2: User config
config = merge_cli_args(config, args)    # Step 3: CLI args
validate_config(config)                  # Step 4: Validate

Clean implementation with clear precedence: CLI > User Config > Defaults

6. Configurable max_virtual_transforms

# binary_serializer.py:20-40
_max_virtual_transforms = 50
def set_max_virtual_transforms(value: int) -> None:
    global _max_virtual_transforms, MAX_VIRTUAL_TRANSFORMS
    if value <= 0:
        raise ValueError("max_virtual_transforms must be positive")
    _max_virtual_transforms = value
    MAX_VIRTUAL_TRANSFORMS = value

Good integration with existing code. The limit is now configurable via TOML.


🔍 Issues Found

Medium Priority

1. Using print() for warnings instead of sys.stderr

Location: config.py:387-389

if unknown:
    print(f"WARNING: Unknown keys in {user_config_path}:")
    for key in unknown:
        print(f"  - {key}")

Issue: Warnings go to stdout instead of stderr. This can interfere with log parsing/redirection.

Recommendation:

import sys
if unknown:
    print(f"WARNING: Unknown keys in {user_config_path}:", file=sys.stderr)
    for key in unknown:
        print(f"  - {key}", file=sys.stderr)

Why this matters: Standard practice is warnings → stderr, normal output → stdout. This helps when users redirect output.


2. CLI argument name confusion: --user-config vs --config

Location: server.py:1925

parser.add_argument(
    "--user-config",  # Should this be --config?
    type=Path,
    metavar="FILE",
    help="Path to user TOML configuration file (overrides defaults)",
)

Issue: The PR description and previous review mentioned --config, but the code uses --user-config.

Current behavior:

styly-netsync-server --user-config myconfig.toml  # ✓ Works
styly-netsync-server --config myconfig.toml       # ✗ Doesn't work

Recommendation: Choose one and be consistent:

  • Option A: Rename to --config (shorter, matches common tools like docker, kubectl)
  • Option B: Keep --user-config and update documentation

I'd recommend Option A (--config) for brevity, but either is fine if documented consistently.


3. Missing cross-field validation for timing values

Location: config.py:209-282 (validate_config function)

Issue: Individual timing values are validated (must be positive), but logical relationships aren't checked.

Example problematic config:

base_broadcast_interval = 1.0
dirty_threshold = 2.0  # Should be <= base_broadcast_interval

Current behavior: Passes validation, but logically incorrect (dirty_threshold should be ≤ base for adaptive rate limiting to work correctly).

Recommendation: Add cross-field validation:

# After line 241 in validate_config()
if config.dirty_threshold > config.base_broadcast_interval:
    errors.append(
        f"dirty_threshold ({config.dirty_threshold}) should be <= "
        f"base_broadcast_interval ({config.base_broadcast_interval})"
    )

if config.base_broadcast_interval > config.idle_broadcast_interval:
    errors.append(
        f"base_broadcast_interval ({config.base_broadcast_interval}) should be <= "
        f"idle_broadcast_interval ({config.idle_broadcast_interval})"
    )

Severity: Medium - won't cause crashes, but could lead to unexpected server behavior.


Low Priority (Nice to Have)

4. Empty string → None conversion lacks explanation

Location: config.py:183-185

if key in ("log_dir", "log_rotation", "log_retention"):
    if value == "":
        value = None

Recommendation: Add a comment explaining why TOML empty strings need conversion:

# Convert empty strings to None for optional logging fields
# TOML uses "" for empty strings, but our config expects None
if key in ("log_dir", "log_rotation", "log_retention"):
    if value == "":
        value = None

5. Consider catching TypeError in user config loading

Location: config.py:395

config = dataclass_replace(config, **config_data)

Issue: If user config has wrong types (e.g., string instead of int), the TypeError from dataclass construction might be cryptic.

Current error handling (server.py:1998-2000):

except Exception as e:
    print(f"ERROR: Failed to load configuration: {e}")
    sys.exit(1)

This catches it, but the error message could be clearer.

Recommendation: Add specific handling:

try:
    config = dataclass_replace(config, **config_data)
except TypeError as e:
    raise ConfigurationError([
        f"Invalid type in user config file: {e}. "
        "Check that ports are integers, timing values are numbers, etc."
    ]) from e

6. --no-server-discovery boolean flag naming

Location: config.py:336-337

if hasattr(args, "no_server_discovery") and args.no_server_discovery:
    updates["enable_server_discovery"] = False

Minor clarity issue: The check args.no_server_discovery is only True when the flag is present. This is correct, but a comment would help:

# Only disable discovery when --no-server-discovery flag is explicitly set
if hasattr(args, "no_server_discovery") and args.no_server_discovery:
    updates["enable_server_discovery"] = False

🎯 Improvements from Previous Review

The team addressed most of the previous review feedback:

Fixed: Used print() instead of logging before logging is configured (but should use stderr - see issue #1 above)
Fixed: CLI argument override bug for --log-level-console
Fixed: Added unknown key detection with warnings
Improved: Simplified architecture with flat config (major improvement!)

⏸️ Partially addressed:

  • CLI argument naming (--user-config vs --config) - still inconsistent with PR description
  • Cross-field validation - not yet implemented

🔒 Security Assessment

No security issues identified

  • Input validation is comprehensive (ports, timing, log levels)
  • File paths handled safely with Path objects
  • No injection risks (SQL, command, path traversal)
  • TOML parsing uses built-in tomllib (safe, no external dependencies)
  • Validation prevents DoS via resource exhaustion (max limits enforced)

🧪 Testing Assessment

Excellent test coverage

Test Statistics:

  • 47 tests across 8 test classes
  • 767 lines of test code
  • Covers: defaults, user config, CLI overrides, validation, errors, edge cases

Test Categories:

  • ✅ Default config loading
  • ✅ User config loading and merging
  • ✅ CLI argument overrides
  • ✅ Validation (ports, timing, NV limits, logging)
  • ✅ Error handling (missing files, invalid TOML, validation errors)
  • ✅ Unknown key detection
  • ✅ Edge cases (empty files, partial configs, type mismatches)

Minor suggestion: Add a test to verify the warning output for unknown keys is formatted correctly.


📋 Code Style & Best Practices

Code follows Python best practices

  • Type hints throughout (ServerConfig, dict[str, Any], etc.)
  • Proper exception handling with custom exception classes
  • Clear naming conventions
  • Good documentation (docstrings on all functions)
  • Follows existing codebase patterns
  • Immutable config updates using dataclass_replace()

🔄 Backward Compatibility

Fully backward compatible

  • All existing CLI args work (--log-dir, --log-level-console, etc.)
  • Server runs without config file (all defaults provided)
  • Default values unchanged from previous behavior
  • No breaking changes to server API or protocol

📁 File Structure

The new files are well-organized:

STYLY-NetSync-Server/
├── src/styly_netsync/
│   ├── config.py           # NEW - Configuration management (405 lines)
│   ├── default.toml        # NEW - Bundled defaults (100 lines)
│   ├── server.py           # MODIFIED - Integration
│   └── binary_serializer.py # MODIFIED - Configurable max_virtual_transforms
├── examples/
│   └── config.toml         # NEW - User config example (47 lines)
└── tests/
    └── test_config.py      # NEW - Comprehensive tests (767 lines)

🎓 Architecture Review

Major Improvement: Flat Config Structure

The switch from nested TOML sections to a flat structure is a significant architectural improvement:

Before (nested):

[network]
dealer_port = 5555
pub_port = 5556

[timing]
base_broadcast_interval = 0.1

After (flat):

dealer_port = 5555
pub_port = 5556
base_broadcast_interval = 0.1

Why this is better:

  1. Simpler code - No section flattening logic needed
  2. Easier to understand - Flat namespace is more straightforward
  3. Better for validation - Direct key → value mapping
  4. Fewer edge cases - No nested structure to handle

The code in config.py is much cleaner as a result. Great refactoring!


✅ Recommendations Summary

Should Fix Before Merge

  1. Use sys.stderr for warnings (config.py:387-389) - 2 line change
  2. Clarify CLI argument name - Choose --config or --user-config and update docs consistently
  3. Add cross-field validation for timing values (config.py:validate_config)

Nice to Have (Optional)

  1. Add comment explaining empty string → None conversion
  2. Add specific TypeError handling for user config type mismatches
  3. Add comment for --no-server-discovery flag logic

🎯 Final Verdict

This PR is in excellent shape and represents a well-designed, well-tested feature. The code quality is high, test coverage is comprehensive, and the flat configuration architecture is a smart design choice.

Approval Status: ✅ Approve with Minor Fixes

Recommended actions:

  1. Fix the 3 "Should Fix Before Merge" items above (quick fixes)
  2. Merge to develop after fixes

Overall assessment:

  • ✅ Code quality: Excellent
  • ✅ Test coverage: 47 tests, comprehensive
  • ✅ Architecture: Clean, well-designed
  • ✅ Documentation: Good (examples/config.toml, docstrings)
  • ✅ Backward compatibility: Maintained
  • ⚠️ Minor issues: 3 items to fix before merge

Great work on this feature! The TOML configuration support is a valuable addition to STYLY-NetSync. 🎉


Branch: feature/248-config-file-support

@from2001
Copy link
Collaborator

@copilot Review the latest code.

Copy link
Contributor

Copilot AI commented Jan 18, 2026

@from2001 I've opened a new pull request, #287, to work on those changes. Once the pull request is ready, I'll request review from you.

@from2001
Copy link
Collaborator

I made following changes.

  1. f6fd7d1 - Update config file argument from --user-config to --config for consistency
  • Renamed CLI argument --user-config to --config for simpler, more intuitive usage
  • Updated config loading logic and help text in config.py
  • Modified server startup to use the new argument name
  1. 43973de - Remove example user configuration file and update default config instructions
  • Deleted examples/config.toml (separate example file)
  • Updated default.toml header with instructions for users to copy and customize it
  • Consolidated configuration documentation into a single file
  1. be6fe58 - Add validation tests for timing relationships and update config file references
  • Added new tests to validate timing configuration relationships (e.g., dirty_threshold < base_broadcast_interval < idle_broadcast_interval)
  • Updated existing tests to use --config instead of --user-config
  • Improved test coverage for configuration edge cases
  1. ef667a9 - Update README to include configuration file usage instructions
  • Added new "Configuration" section to README.md
  • Documented how to copy default.toml, customize settings, and use --config option
  • Included example minimal configuration and link to download from GitHub

@from2001 from2001 merged commit c26a711 into develop Jan 18, 2026
@from2001 from2001 deleted the feature/248-config-file-support branch January 18, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add configuration file support for server settings

3 participants