qq_lib.core.field_coupling

Utilities for defining and enforcing coupled fields in dataclasses.

This module provides FieldCoupling for specifying dominance-ordered relationships among multiple fields, and the @coupled_fields decorator for automatically enforcing these rules in a dataclass's __post_init__.

  1# Released under MIT License.
  2# Copyright (c) 2025-2026 Ladislav Bartos and Robert Vacha Lab
  3
  4"""
  5Utilities for defining and enforcing coupled fields in dataclasses.
  6
  7This module provides `FieldCoupling` for specifying dominance-ordered
  8relationships among multiple fields, and the `@coupled_fields` decorator for
  9automatically enforcing these rules in a dataclass's `__post_init__`.
 10"""
 11
 12from typing import Any, Protocol
 13
 14
 15class FieldCoupling:
 16    """
 17    Represents a coupling among multiple fields, ordered by dominance.
 18
 19    The earlier a field appears in `fields`, the more dominant it is.
 20    If multiple fields have values in an instance, only the most dominant
 21    one is preserved; all others are set to None.
 22    """
 23
 24    def __init__(self, *fields: str):
 25        if len(fields) < 2:
 26            raise ValueError("FieldCoupling requires at least two fields")
 27        self.fields = list(fields)
 28
 29    def contains(self, field_name: str) -> bool:
 30        """Return True if the field participates in this coupling."""
 31        return field_name in self.fields
 32
 33    def get_fields(self) -> tuple[str, ...]:
 34        """Return all coupled fields as a tuple."""
 35        return tuple(self.fields)
 36
 37    def has_value(self, instance: Any) -> bool:
 38        """Return True if any of the coupled fields has a non-None value."""
 39        return any(getattr(instance, field) is not None for field in self.fields)
 40
 41    def get_most_dominant_set_field(self, instance: Any) -> str | None:
 42        """
 43        Return the name of the most dominant field that has a non-None value,
 44        or None if none of them do.
 45        """
 46        for field in self.fields:
 47            if getattr(instance, field) is not None:
 48                return field
 49
 50        return None
 51
 52    def enforce(self, instance: Any):
 53        """
 54        Enforce dominance rules: only the most dominant field that is set
 55        keeps its value; others are reset to None.
 56        """
 57        dominant_set_field = self.get_most_dominant_set_field(instance)
 58        if dominant_set_field is None:
 59            return
 60
 61        for field in self.fields:
 62            if field != dominant_set_field:
 63                setattr(instance, field, None)
 64
 65
 66def coupled_fields(*couplings: FieldCoupling):
 67    """
 68    Class decorator that enforces multi-field coupling rules in __post_init__.
 69    """
 70
 71    def decorator(cls):
 72        cls._field_couplings = couplings
 73        original_post_init = getattr(cls, "__post_init__", None)
 74
 75        def __post_init__(self):
 76            for coupling in self._field_couplings:
 77                coupling.enforce(self)
 78
 79            if original_post_init:
 80                original_post_init(self)
 81
 82        @staticmethod
 83        def get_coupling_for_field(field_name: str) -> FieldCoupling | None:
 84            for coupling in cls._field_couplings:
 85                if coupling.contains(field_name):
 86                    return coupling
 87
 88            return None
 89
 90        cls.__post_init__ = __post_init__
 91        cls.get_coupling_for_field = get_coupling_for_field
 92        return cls
 93
 94    return decorator
 95
 96
 97class HasCouplingMethods(Protocol):
 98    """Protocol for classes decorated with @coupled_fields."""
 99
100    _field_couplings: tuple[FieldCoupling, ...]
101
102    @staticmethod
103    def get_coupling_for_field(field_name: str) -> FieldCoupling | None:
104        """Return the FieldCoupling that contains the given field name, or None."""
105        ...
class FieldCoupling:
16class FieldCoupling:
17    """
18    Represents a coupling among multiple fields, ordered by dominance.
19
20    The earlier a field appears in `fields`, the more dominant it is.
21    If multiple fields have values in an instance, only the most dominant
22    one is preserved; all others are set to None.
23    """
24
25    def __init__(self, *fields: str):
26        if len(fields) < 2:
27            raise ValueError("FieldCoupling requires at least two fields")
28        self.fields = list(fields)
29
30    def contains(self, field_name: str) -> bool:
31        """Return True if the field participates in this coupling."""
32        return field_name in self.fields
33
34    def get_fields(self) -> tuple[str, ...]:
35        """Return all coupled fields as a tuple."""
36        return tuple(self.fields)
37
38    def has_value(self, instance: Any) -> bool:
39        """Return True if any of the coupled fields has a non-None value."""
40        return any(getattr(instance, field) is not None for field in self.fields)
41
42    def get_most_dominant_set_field(self, instance: Any) -> str | None:
43        """
44        Return the name of the most dominant field that has a non-None value,
45        or None if none of them do.
46        """
47        for field in self.fields:
48            if getattr(instance, field) is not None:
49                return field
50
51        return None
52
53    def enforce(self, instance: Any):
54        """
55        Enforce dominance rules: only the most dominant field that is set
56        keeps its value; others are reset to None.
57        """
58        dominant_set_field = self.get_most_dominant_set_field(instance)
59        if dominant_set_field is None:
60            return
61
62        for field in self.fields:
63            if field != dominant_set_field:
64                setattr(instance, field, None)

Represents a coupling among multiple fields, ordered by dominance.

The earlier a field appears in fields, the more dominant it is. If multiple fields have values in an instance, only the most dominant one is preserved; all others are set to None.

FieldCoupling(*fields: str)
25    def __init__(self, *fields: str):
26        if len(fields) < 2:
27            raise ValueError("FieldCoupling requires at least two fields")
28        self.fields = list(fields)
fields
def contains(self, field_name: str) -> bool:
30    def contains(self, field_name: str) -> bool:
31        """Return True if the field participates in this coupling."""
32        return field_name in self.fields

Return True if the field participates in this coupling.

def get_fields(self) -> tuple[str, ...]:
34    def get_fields(self) -> tuple[str, ...]:
35        """Return all coupled fields as a tuple."""
36        return tuple(self.fields)

Return all coupled fields as a tuple.

def has_value(self, instance: Any) -> bool:
38    def has_value(self, instance: Any) -> bool:
39        """Return True if any of the coupled fields has a non-None value."""
40        return any(getattr(instance, field) is not None for field in self.fields)

Return True if any of the coupled fields has a non-None value.

def get_most_dominant_set_field(self, instance: Any) -> str | None:
42    def get_most_dominant_set_field(self, instance: Any) -> str | None:
43        """
44        Return the name of the most dominant field that has a non-None value,
45        or None if none of them do.
46        """
47        for field in self.fields:
48            if getattr(instance, field) is not None:
49                return field
50
51        return None

Return the name of the most dominant field that has a non-None value, or None if none of them do.

def enforce(self, instance: Any):
53    def enforce(self, instance: Any):
54        """
55        Enforce dominance rules: only the most dominant field that is set
56        keeps its value; others are reset to None.
57        """
58        dominant_set_field = self.get_most_dominant_set_field(instance)
59        if dominant_set_field is None:
60            return
61
62        for field in self.fields:
63            if field != dominant_set_field:
64                setattr(instance, field, None)

Enforce dominance rules: only the most dominant field that is set keeps its value; others are reset to None.

def coupled_fields(*couplings: FieldCoupling):
67def coupled_fields(*couplings: FieldCoupling):
68    """
69    Class decorator that enforces multi-field coupling rules in __post_init__.
70    """
71
72    def decorator(cls):
73        cls._field_couplings = couplings
74        original_post_init = getattr(cls, "__post_init__", None)
75
76        def __post_init__(self):
77            for coupling in self._field_couplings:
78                coupling.enforce(self)
79
80            if original_post_init:
81                original_post_init(self)
82
83        @staticmethod
84        def get_coupling_for_field(field_name: str) -> FieldCoupling | None:
85            for coupling in cls._field_couplings:
86                if coupling.contains(field_name):
87                    return coupling
88
89            return None
90
91        cls.__post_init__ = __post_init__
92        cls.get_coupling_for_field = get_coupling_for_field
93        return cls
94
95    return decorator

Class decorator that enforces multi-field coupling rules in __post_init__.

class HasCouplingMethods(typing.Protocol):
 98class HasCouplingMethods(Protocol):
 99    """Protocol for classes decorated with @coupled_fields."""
100
101    _field_couplings: tuple[FieldCoupling, ...]
102
103    @staticmethod
104    def get_coupling_for_field(field_name: str) -> FieldCoupling | None:
105        """Return the FieldCoupling that contains the given field name, or None."""
106        ...

Protocol for classes decorated with @coupled_fields.

HasCouplingMethods(*args, **kwargs)
1945def _no_init_or_replace_init(self, *args, **kwargs):
1946    cls = type(self)
1947
1948    if cls._is_protocol:
1949        raise TypeError('Protocols cannot be instantiated')
1950
1951    # Already using a custom `__init__`. No need to calculate correct
1952    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1953    if cls.__init__ is not _no_init_or_replace_init:
1954        return
1955
1956    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1957    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1958    # searches for a proper new `__init__` in the MRO. The new `__init__`
1959    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1960    # instantiation of the protocol subclass will thus use the new
1961    # `__init__` and no longer call `_no_init_or_replace_init`.
1962    for base in cls.__mro__:
1963        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1964        if init is not _no_init_or_replace_init:
1965            cls.__init__ = init
1966            break
1967    else:
1968        # should not happen
1969        cls.__init__ = object.__init__
1970
1971    cls.__init__(self, *args, **kwargs)
@staticmethod
def get_coupling_for_field(field_name: str) -> FieldCoupling | None:
103    @staticmethod
104    def get_coupling_for_field(field_name: str) -> FieldCoupling | None:
105        """Return the FieldCoupling that contains the given field name, or None."""
106        ...

Return the FieldCoupling that contains the given field name, or None.