Skip to content

gwmock.cli.utils.config_state

gwmock.cli.utils.config_state

Pure state management for the interactive config editor.

ConfigState holds the current configuration as a plain dict, provides section-aware getters/setters with type coercion, and validates via the existing Pydantic Config model. No I/O beyond YAML loading — the TUI layer (config_editor.py) is responsible for display.

gwmock.cli.utils.config_state.ConfigState

Mutable, section-aware wrapper around a gwmock config dict.

Source code in src/gwmock/cli/utils/config_state.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
class ConfigState:
    """Mutable, section-aware wrapper around a gwmock config dict."""

    def __init__(self, data: dict[str, Any] | None = None) -> None:
        self._data: dict[str, Any] = data if data is not None else {}
        self.config_file: str | None = None

    # -- section access ----------------------------------------------------- #

    def set(self, section: str, key: str, raw_value: str) -> None:
        """Set *key* in *section* from a string *raw_value*."""
        keys_map = SECTION_KEYS.get(section)
        if keys_map is None:
            raise ValueError(f"Unknown section: {section}")
        path = keys_map.get(key)
        if path is None:
            valid = ", ".join(keys_map)
            raise ValueError(f"Unknown key '{key}' for /{section}. Valid keys: {valid}")
        types_map = SECTION_TYPES.get(section, {})
        target = types_map.get(key, str)
        value = _coerce(raw_value, target)
        _validate_value(key, value)
        _set(self._data, path, value)

    def get(self, section: str, key: str) -> Any:
        """Get *key* from *section*, or ``None`` if unset."""
        keys_map = SECTION_KEYS.get(section, {})
        path = keys_map.get(key)
        if path is None:
            return None
        return _get(self._data, path)

    def get_section(self, section: str) -> dict[str, Any] | None:
        """Return the raw dict for *section*, or ``None`` if empty."""
        mapping = {
            "noise": ["orchestration", "noise"],
            "signal": ["orchestration", "signal"],
            "population": ["orchestration", "population"],
            "globals": ["globals"],
            "batch": ["batch"],
        }
        path = mapping.get(section)
        if path is None:
            return None
        result = _get(self._data, path)
        if isinstance(result, dict) and result:
            return result
        return None

    def set_batch_resource(self, key: str, raw_value: str) -> None:
        _set(self._data, ["batch", "resources", key], raw_value)

    def set_batch_submit(self, key: str, raw_value: str) -> None:
        _set(self._data, ["batch", "submit", key], raw_value)

    # -- glitch helpers ---------------------------------------------------- #

    def add_glitch(self, kind: str) -> int:
        glitches = _get(self._data, ["orchestration", "noise", "arguments", "glitches"], [])
        if not isinstance(glitches, list):
            glitches = []
        glitches.append({"kind": kind})
        _set(self._data, ["orchestration", "noise", "arguments", "glitches"], glitches)
        return len(glitches) - 1

    def remove_glitch(self, index: int) -> dict[str, Any]:
        glitches = _get(self._data, ["orchestration", "noise", "arguments", "glitches"], [])
        if not isinstance(glitches, list) or not (0 <= index < len(glitches)):
            raise IndexError(f"Glitch index {index} out of range (have {len(glitches)} glitches)")
        removed = glitches.pop(index)
        if glitches:
            _set(self._data, ["orchestration", "noise", "arguments", "glitches"], glitches)
        else:
            _delete(self._data, ["orchestration", "noise", "arguments", "glitches"])
        return removed

    # -- reset ------------------------------------------------------------- #

    def reset(self, section: str | None = None) -> None:
        if section is None:
            self._data = {}
            return
        mapping = {
            "noise": ["orchestration", "noise"],
            "signal": ["orchestration", "signal"],
            "population": ["orchestration", "population"],
            "globals": ["globals"],
            "batch": ["batch"],
        }
        path = mapping.get(section)
        if path is None:
            raise ValueError(f"Unknown section: {section}")
        _delete(self._data, path)

    # -- serialisation ----------------------------------------------------- #

    def to_dict(self) -> dict[str, Any]:
        return _clean_empty(self._data)

    def load(self, path: Path) -> None:
        with open(path, encoding="utf-8") as f:
            data = yaml.safe_load(f)
        if not isinstance(data, dict):
            raise ValueError("Configuration file must contain a YAML mapping")
        self._data = _clean_none(data)

    def validate(self) -> tuple[bool, str]:
        from gwmock.cli.utils.config import Config  # noqa: PLC0415

        try:
            Config(**self.to_dict())
            return True, ""
        except Exception as e:
            return False, str(e)

    @property
    def is_empty(self) -> bool:
        return not self._data or not _clean_empty(self._data)

gwmock.cli.utils.config_state.ConfigState.set(section, key, raw_value)

Set key in section from a string raw_value.

Source code in src/gwmock/cli/utils/config_state.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def set(self, section: str, key: str, raw_value: str) -> None:
    """Set *key* in *section* from a string *raw_value*."""
    keys_map = SECTION_KEYS.get(section)
    if keys_map is None:
        raise ValueError(f"Unknown section: {section}")
    path = keys_map.get(key)
    if path is None:
        valid = ", ".join(keys_map)
        raise ValueError(f"Unknown key '{key}' for /{section}. Valid keys: {valid}")
    types_map = SECTION_TYPES.get(section, {})
    target = types_map.get(key, str)
    value = _coerce(raw_value, target)
    _validate_value(key, value)
    _set(self._data, path, value)

gwmock.cli.utils.config_state.ConfigState.get(section, key)

Get key from section, or None if unset.

Source code in src/gwmock/cli/utils/config_state.py
322
323
324
325
326
327
328
def get(self, section: str, key: str) -> Any:
    """Get *key* from *section*, or ``None`` if unset."""
    keys_map = SECTION_KEYS.get(section, {})
    path = keys_map.get(key)
    if path is None:
        return None
    return _get(self._data, path)

gwmock.cli.utils.config_state.ConfigState.get_section(section)

Return the raw dict for section, or None if empty.

Source code in src/gwmock/cli/utils/config_state.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def get_section(self, section: str) -> dict[str, Any] | None:
    """Return the raw dict for *section*, or ``None`` if empty."""
    mapping = {
        "noise": ["orchestration", "noise"],
        "signal": ["orchestration", "signal"],
        "population": ["orchestration", "population"],
        "globals": ["globals"],
        "batch": ["batch"],
    }
    path = mapping.get(section)
    if path is None:
        return None
    result = _get(self._data, path)
    if isinstance(result, dict) and result:
        return result
    return None