Source code for gunz_cm.structs.conflict_policy

"""
Conflict policy for file-write operations (DOP Phase 7, v2.24.0).

This module introduces :data:`ConflictPolicy`, a ``Literal`` type that
replaces the historical ``(overwrite: bool, exist_ok: bool)`` XOR pair
on the COO writers and reconstruction wrappers.

DOP alignment
-------------
- **Principle 4 (make illegal states unrepresentable):** the legacy
  pair ``(overwrite=False, exist_ok=False)`` raises, ``(True, False)``
  overwrites silently, ``(False, True)`` skips silently, ``(True, True)``
  is ambiguous (logs a warning in some sites). Three of the four
  combinations are legal, only three are meaningful, and one is silently
  ill-defined. Phase 7 collapses the pair into a single named mode
  (``"error"``, ``"overwrite"``, ``"skip"``) so the illegal fourth
  combination cannot be expressed.

- **Principle 1 (separate code from data):** the policy is a literal
  type — pure data — separate from the file-writing code that consumes
  it. The conversion from the legacy ``(overwrite, exist_ok)`` pair to
  a ``ConflictPolicy`` lives in :func:`from_legacy_flags`, which is
  the only place that touches both shapes during the deprecation window.

Pattern: bool-pair → Literal
----------------------------
Five functions currently accept the ``(overwrite, exist_ok)`` pair:

- ``gunz_cm.converters.coo.convert_to_cm_coo``
- ``gunz_cm.converters.coo.convert_all_intra_to_cm_coo`` (asymmetric:
  only ``overwrite`` exposed, ``exist_ok`` hardcoded internally)
- ``gunz_cm.reconstructions.implementations.superrec.gen_superrec_coo``
- ``gunz_cm.reconstructions.implementations.shneigh.gen_shneigh_coo``
- ``gunz_cm.reconstructions.implementations.h3dg.gen_h3dg_coo``
  (asymmetric: only ``overwrite`` exposed)

Phase 7 introduces ``on_conflict: ConflictPolicy = "error"`` on all
five. The legacy ``overwrite`` and ``exist_ok`` kwargs remain as
back-compat inputs for one release (v2.24.0) with ``DeprecationWarning``.
Phase 8 (v2.25.0) will remove them.

Examples
--------
>>> from gunz_cm.structs.conflict_policy import ConflictPolicy, from_legacy_flags
>>> from_legacy_flags(overwrite=False, exist_ok=False)
'error'
>>> from_legacy_flags(overwrite=True, exist_ok=False)
'overwrite'
>>> from_legacy_flags(overwrite=False, exist_ok=True)
'skip'

>>> # The (True, True) combination is ill-defined and produces a clear error.
>>> from_legacy_flags(overwrite=True, exist_ok=True)
Traceback (most recent call last):
    ...
ValueError: (overwrite=True, exist_ok=True) is ambiguous — use on_conflict instead.
"""
from __future__ import annotations

import typing as t


# ConflictPolicy: the canonical named modes for "what to do when the
# output file already exists."
#
# - "error": raise FileExistsError. The safe default for batch jobs.
# - "overwrite": silently replace the existing file. Use when the caller
#   has verified the existing file is stale (e.g. via cache invalidation).
# - "skip": log a message and return without writing. Use in long-running
#   pipelines that should be idempotent across re-runs.
ConflictPolicy = t.Literal["error", "overwrite", "skip"]


[docs]def from_legacy_flags(*, overwrite: bool, exist_ok: bool) -> ConflictPolicy: """Translate the legacy ``(overwrite, exist_ok)`` pair to a :data:`ConflictPolicy`. This is the single point of conversion during the v2.24.0 deprecation window. It exists so that: 1. The four legal ``(overwrite, exist_ok)`` combinations map to exactly one of the three named modes. 2. The illegal ``(True, True)`` combination produces a clear ``ValueError`` rather than silently falling through to one of the other branches. Mapping ------- - ``(False, False)`` → ``"error"`` (raise on conflict) - ``(True, False)`` → ``"overwrite"`` (replace existing) - ``(False, True)`` → ``"skip"`` (log + return) - ``(True, True)`` → ``ValueError`` (ambiguous — prefer the new ``on_conflict`` parameter for clarity) Parameters ---------- overwrite The legacy ``overwrite`` flag. exist_ok The legacy ``exist_ok`` flag. Returns ------- ConflictPolicy The corresponding named mode. Raises ------ ValueError If both flags are ``True`` — the combination is ambiguous. Examples -------- >>> from_legacy_flags(overwrite=False, exist_ok=False) 'error' >>> from_legacy_flags(overwrite=True, exist_ok=False) 'overwrite' >>> from_legacy_flags(overwrite=False, exist_ok=True) 'skip' """ if overwrite and exist_ok: raise ValueError( "(overwrite=True, exist_ok=True) is ambiguous — use the new " "on_conflict parameter instead. See gunz_cm.structs.ConflictPolicy." ) if overwrite: return "overwrite" if exist_ok: return "skip" return "error"
__all__ = [ "ConflictPolicy", "from_legacy_flags", ]