diff --git a/patroni/dcs/__init__.py b/patroni/dcs/__init__.py index 29a4d766..4bcac3ce 100644 --- a/patroni/dcs/__init__.py +++ b/patroni/dcs/__init__.py @@ -555,36 +555,50 @@ class TimelineHistory(NamedTuple): return TimelineHistory(version, value, lines) -class Cluster(NamedTuple): - """Immutable object (namedtuple) which represents PostgreSQL cluster. +class Cluster(NamedTuple('Cluster', + [('initialize', Optional[str]), + ('config', Optional[ClusterConfig]), + ('leader', Optional[Leader]), + ('last_lsn', int), + ('members', List[Member]), + ('failover', Optional[Failover]), + ('sync', SyncState), + ('history', Optional[TimelineHistory]), + ('slots', Optional[Dict[str, int]]), + ('failsafe', Optional[Dict[str, str]]), + ('workers', Dict[int, 'Cluster'])])): + """Immutable object (namedtuple) which represents PostgreSQL or Citus cluster. + + .. note:: + We are using an old-style attribute declaration here because otherwise it is not possible to override `__new__` + method. Without it the *workers* by default gets always the same :class:`dict` object that could be mutated. + Consists of the following fields: - :param initialize: shows whether this cluster has initialization key stored in DC or not. - :param config: global dynamic configuration, reference to `ClusterConfig` object - :param leader: `Leader` object which represents current leader of the cluster - :param last_lsn: int or long object containing position of last known leader LSN. - This value is stored in the `/status` key or `/optime/leader` (legacy) key - :param members: list of Member object, all PostgreSQL cluster members including leader - :param failover: reference to `Failover` object - :param sync: reference to `SyncState` object, last observed synchronous replication state. - :param history: reference to `TimelineHistory` object - :param slots: state of permanent logical replication slots on the primary in the format: {"slot_name": int} - :param failsafe: failsafe topology. Node is allowed to become the leader only if its name is found in this list. - :param workers: workers of the Citus cluster, optional. Format: {int(group): Cluster()} + + :ivar initialize: shows whether this cluster has initialization key stored in DC or not. + :ivar config: global dynamic configuration, reference to `ClusterConfig` object. + :ivar leader: :class:`Leader` object which represents current leader of the cluster. + :ivar last_lsn: :class:int object containing position of last known leader LSN. + This value is stored in the `/status` key or `/optime/leader` (legacy) key. + :ivar members: list of:class:` Member` objects, all PostgreSQL cluster members including leader + :ivar failover: reference to :class:`Failover` object. + :ivar sync: reference to :class:`SyncState` object, last observed synchronous replication state. + :ivar history: reference to `TimelineHistory` object. + :ivar slots: state of permanent logical replication slots on the primary in the format: {"slot_name": int}. + :ivar failsafe: failsafe topology. Node is allowed to become the leader only if its name is found in this list. + :ivar workers: dictionary of workers of the Citus cluster, optional. Each key is an :class:`int` representing + the group, and the corresponding value is a :class:`Cluster` instance. """ - initialize: Optional[str] - config: Optional[ClusterConfig] - leader: Optional[Leader] - last_lsn: int - members: List[Member] - failover: Optional[Failover] - sync: SyncState - history: Optional[TimelineHistory] - slots: Optional[Dict[str, int]] - failsafe: Optional[Dict[str, str]] - workers: Dict[int, 'Cluster'] = {} + + def __new__(cls, *args: Any, **kwargs: Any): + """Make workers argument optional and set it to an empty dict object.""" + if len(args) < len(cls._fields) and 'workers' not in kwargs: + kwargs['workers'] = {} + return super(Cluster, cls).__new__(cls, *args, **kwargs) @staticmethod def empty() -> 'Cluster': + """Produce an empty :class:`Cluster` instance.""" return Cluster(None, None, None, 0, [], None, SyncState.empty(), None, None, None) def is_empty(self):